Skip to content

9. An agent under the gate ​

The previous chapters used a model inside FoBiS. An agent (Claude Code, Codex, any client of the Model Context Protocol) works the other way round: the agent is the model, and FoBiS is a tool it calls. fobis mcp serves FoBiS's facts and checks over MCP:

bash
claude mcp add fobis -- fobis mcp       # Claude Code; other clients take the same command

A minimal client, in Python, speaks the protocol the way an agent does, one JSON-RPC request per line:

tools/mcp_client.py
python
"""A minimal MCP client: what an agent does with `fobis mcp`, one request per line on the server's stdin.

python3 tools/mcp_client.py list                   the tools, read-only ones marked
python3 tools/mcp_client.py call TOOL '{"k": "v"}'   call one tool, print its text result
"""

import json
import subprocess
import sys

server = subprocess.Popen(["fobis", "mcp"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)


def request(ident, method, params=None):
    message = {"jsonrpc": "2.0", "id": ident, "method": method, "params": params or {}}
    server.stdin.write(json.dumps(message) + "\n")
    server.stdin.flush()
    return json.loads(server.stdout.readline())


hello = request(1, "initialize", {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "demo"}})
server.stdin.write(json.dumps({"jsonrpc": "2.0", "method": "notifications/initialized"}) + "\n")
if sys.argv[1] == "list":
    info = hello["result"]["serverInfo"]
    print(f"{info['name']}: protocol {hello['result']['protocolVersion']}")
    for tool in request(2, "tools/list")["result"]["tools"]:
        mark = "read-only" if tool["annotations"].get("readOnlyHint") else "writes"
        print(f"  {tool['name']:<20} {mark:<10} {tool['title']}")
else:
    reply = request(2, "tools/call", {"name": sys.argv[2], "arguments": json.loads(sys.argv[3])})["result"]
    print("error" if reply.get("isError") else "result", "of", sys.argv[2])
    print(reply["content"][0]["text"])
server.stdin.close()
server.wait()
console
$ python3 tools/mcp_client.py list
fobis: protocol 2025-06-18
  lint                 read-only  Lint Fortran sources
  review               read-only  Review a change
  list_rules           read-only  List lint rules
  explain_rule         read-only  Explain a lint rule
  kb_search            read-only  Search the knowledge packs
  kb_show              read-only  Show a knowledge-pack section
  ask_context          read-only  Retrieve grounding for a question
  query                read-only  Query the code graph
  agents_md            read-only  Project briefing for agents
  bindings             read-only  Plan C and Python bindings
  introspect           read-only  Project build facts
  check                read-only  Check the dependency graph
  tree                 read-only  Dependency tree
  merge_conflicts      read-only  Analyse merge conflicts
  commit_facts         read-only  Facts of the staged change
  testgen_skeleton     read-only  Test skeleton for a module
  ecosystem_status     read-only  Ecosystem status
  ecosystem_impact     read-only  Impact of an API change
  case_list            read-only  List the declared cases
  provenance           read-only  Provenance of a result
  build                writes     Build the project
  test                 writes     Run the tests
  case_run             writes     Run cases
  verify               writes     Verify results against references
  matrix               writes     Build and test every mode x shadow
  assess               writes     Assess numerical error and uncertainty
  diff_run             writes     Compare results with a revision
  bench                writes     Measure run time
  profile              writes     Profile a case
  modernize            writes     Verify mechanical modernisation
  gate                 writes     One verdict for a change
  clean                writes     Clean build products
  fetch                writes     Fetch dependencies

The read-only tools give facts: where simpson is called, from the code graph rather than a text search:

console
$ python3 tools/mcp_client.py call query '{"kind": "callers", "argument": "simpson"}'
result of query
{
 "kind": "callers",
 "argument": "simpson",
 "items": [
  {
   "caller": "area",
   "path": "src/area.F90",
   "line": 18,
   "name": "simpson",
   "kind": "function",
   "resolved": [
    "quad_rules::simpson"
   ],
   "how": "unique",
   "text": "print '(A, ES24.17)', 'simpson = ', simpson(sine, 0._R8P, pi, n)"
  },
  {
   "caller": "integrate",
   "path": "src/integrate.F90",
   "line": 14,
   "name": "simpson",
   "kind": "function",
   "resolved": [
    "quad_rules::simpson"
   ],
   "how": "unique",
   "text": "print '(I6, 2ES18.4)', n, abs(trapezoid(sine, 0._R8P, pi, n) - 2._R8P), abs(simpson(sine, 0._R8P,..."
  },
  {
   "caller": "integrate",
   "path": "src/integrate.F90",
   "line": 17,
   "name": "simpson",
   "kind": "function",
   "resolved": [
    "quad_rules::simpson"
   ],
   "how": "unique",
   "text": "print '(A)', 'Simpson, 64 intervals: '//trim(str(simpson(sine, 0._R8P, pi, 64), no_sign=.true., c..."
  },
  {
   "caller": "integrate_mpi",
   "path": "src/integrate_mpi.F90",
   "line": 20,
   "name": "simpson",
   "kind": "function",
   "resolved": [
    "quad_rules::simpson"
   ],
   "how": "unique",
   "text": "piece = simpson(sine, a, b, n)"
  },
  {
   "caller": "sweep",
   "path": "src/sweep.F90",
   "line": 18,
   "name": "simpson",
   "kind": "function",
   "resolved": [
    "quad_rules::simpson"
   ],
   "how": "unique",
   "text": "error(2, k) = abs(simpson(sine, 0._R8P, pi, n) - 2._R8P)"
  },
  {
   "caller": "test_order",
   "path": "test/test_order.F90",
   "line": 12,
   "name": "simpson",
   "kind": "function",
   "resolved": [
    "quad_rules::simpson"
   ],
   "how": "unique",
   "text": "p_simpson = log(abs(simpson(sine, 0._R8P, pi, 16) - 2._R8P) / abs(simpson(sine, 0._R8P, pi, 32) -..."
  },
  {
   "caller": "test_order",
   "path": "test/test_order.F90",
   "line": 12,
   "name": "simpson",
   "kind": "function",
   "resolved": [
    "quad_rules::simpson"
   ],
   "how": "unique",
   "text": "p_simpson = log(abs(simpson(sine, 0._R8P, pi, 16) - 2._R8P) / abs(simpson(sine, 0._R8P, pi, 32) -..."
  }
 ],
 "notes": [],
 "files": 13
}

The agent's edit ​

The agent "simplifies" Simpson's even-node loop, and gets the bound wrong:

console
$ git diff
diff --git a/src/quad_rules.F90 b/src/quad_rules.F90
index 563c154..4cb1281 100644
--- a/src/quad_rules.F90
+++ b/src/quad_rules.F90
@@ -84,7 +84,7 @@ contains
       odd = odd + f(a + i * h)
    enddo
    even = 0._R8P
-   do i = 2, n - 2, 2
+   do i = 2, n, 2
       even = even + f(a + i * h)
    enddo
    s = (f(a) + f(b) + 4._R8P * odd + 2._R8P * even) * h / 3._R8P

Before it says it is done, it calls the gate:

console
$ python3 tools/mcp_client.py call gate '{"stages": ["build", "test", "verify"]}'
result of gate
{
 "status": "fail",
 "base": "",
 "elapsed": 1.89,
 "stages": [
  {
   "stage": "build",
   "status": "pass",
   "advisory": false,
   "fact": "",
   "details": [],
   "commands": [
    [
     "build",
     "--json"
    ]
   ],
   "elapsed": 0.23
  },
  {
   "stage": "test",
   "status": "fail",
   "advisory": false,
   "fact": "test_exactness: failed: the program stopped itself: error stop at test/test_exactness.F90:10",
   "details": [
    "test_symmetry: failed: the program stopped itself: error stop at test/test_symmetry.F90:12"
   ],
   "commands": [
    [
     "test",
     "--json"
    ]
   ],
   "elapsed": 0.93
  },
  {
   "stage": "verify",
   "status": "pass",
   "advisory": false,
   "fact": "",
   "details": [],
   "commands": [
    [
     "verify",
     "--json"
    ]
   ],
   "elapsed": 0.73
  }
 ]
}

The build passes and the test fails, with the location: test_exactness.F90:10, Simpson's rule on a cubic. The reference of area passes: the bug adds 2 f(b) to the sum, and for sin(x) on [0, pi] f(b) = sin(pi) is zero to rounding. One check rarely sees everything; the gate runs all of them and the agent gets the first failing fact.

fobis agents-md (track 4) writes the project's facts into AGENTS.md, so the agent knows the modes, the cases and the commands before it starts.

What you learned

fobis mcp, the read-only and the writing tools, an agent calling gate. Reference: MCP server, the gate.

This is the end of the tutorials. The guides and the reference cover every command and option.