An agent at work
In one minuteThe Model Context Protocol exchange behind an agent task — "characterise this aerofoil and tell me how far to trust the drag" — shown as the raw tool calls and results.
The requests below are JSON-RPC messages posted to /mcp; any MCP client produces them. The
replies were recorded from a service running revision 5ee1cdc on the CPU (a scratch
instance, not a public deployment); numbers are shortened to the digits shown and … marks
omitted keys and values. Nothing on this page was typed in from memory.
1. Orient#
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "validate_case",
"arguments": {"case": "naca0012_euler", "overrides": {"discretization.order": 3}}}}
{"valid": true, "digest": "e81f2ad4bcafe5b9",
"estimate": {"elements": 256, "dim": 2, "block_size": 64, "degrees_of_freedom": 16384, "viscous": false},
"device": "cpu", "spec": {"…": "…"}, "warnings": []}
A wrong name is answered with a way forward rather than a dead end:
{"ok": false, "status": 404,
"error": {"type": "UnknownPreset", "message": "unknown preset 'naca0013'",
"hint": "available presets: ['bump_euler', 'cylinder_re40', 'naca0012_euler', 'naca0012_euler_3d', …]"}}
2. Analyse with an error estimate#
{"name": "submit_job",
"arguments": {"case": "naca0012_euler", "gradients_of": ["cl", "cd"],
"gradients_wrt": ["alpha", "mach"], "error_estimate": ["cd"], "wait_seconds": 120}}
{"id": "job-…", "state": "running", "device": "cpu", "digest": "e4180d2af8d6dd7e",
"estimate": {"elements": 256, "degrees_of_freedom": 9216, "…": "…"}, "…": "…"}
The job had not finished within the 120 seconds of the call (the kernels of this case had never been compiled on that service, and the machine was busy), so the agent waits again:
{"name": "get_job", "arguments": {"job_id": "job-…", "wait_seconds": 120}}
{"name": "get_job_result", "arguments": {"job_id": "job-…"}}
{"converged": true, "reason": "residual tolerance reached", "residual_norm": 7.06e-13,
"outputs": {"cl": 0.278767, "cd": 0.000949, "cm": -0.002021, "…": "…"},
"gradients": {"derivatives": {"cl": {"alpha": 7.7383, "mach": 0.23041},
"cd": {"alpha": 0.011770, "mach": 7.908e-05}},
"info": {"mode": "adjoint", "linear_solves": [
{"output": "cl", "iterations": 51, "reduction": 7.09e-11, "converged": true},
{"output": "cd", "iterations": 50, "reduction": 7.18e-11, "converged": true}], "…": "…"}},
"error_estimate": {"cd": {"value": 0.000949, "estimate": -0.000771, "corrected": 0.000264, "error": 0.000684,
"indicators": {"_array": true, "shape": [256], "max": 1.19e-04, "…": "…"}, "…": "…"}},
"provenance": {"version": "0.1.0", "device": {"platform": "cpu", "…": "…"}, "…": "…"}}
The agent can now state: lift coefficient 0.279; drag coefficient 0.00095, of which about
0.0007 is estimated to be discretization error on this mesh (the exact inviscid drag is zero)
— and cite the revision that produced it. The derivatives with respect to alpha are per
radian.
3. Ground the claim in the documentation#
{"name": "search_docs", "arguments": {"query": "adjoint-weighted residual effectivity"}}
{"query": "adjoint-weighted residual effectivity",
"results": [{"title": "…", "section": "…", "level": 2, "summary": "…", "path": "….md", "score": 32}, "…"]}
Each hit carries title, section, level, summary, path and score, best first. The
titles are elided here on purpose: the ranking is computed from the pages of the deployed
revision and changes whenever the documentation does. In the recorded exchange six pages were
returned and verification/output-error.md, the page that defines the effectivity, was the
sixth; an agent should read down the list rather than take the first hit.
{"name": "read_doc", "arguments": {"path": "verification/output-error.md"}}
4. A sweep in a session#
{"name": "open_session", "arguments": {"case": "naca0012_euler"}}
{"name": "session_evaluate", "arguments": {"session_id": "ses-…", "inputs": {"alpha_deg": 2.0}}}
{"name": "session_gradients", "arguments": {"session_id": "ses-…", "of": ["cl"], "wrt": ["alpha_deg", "x_surface"]}}
{"name": "session_evaluate", "arguments": {"session_id": "ses-…", "inputs": {"alpha_deg": 2.5}}}
{"name": "close_session", "arguments": {"session_id": "ses-…"}}
In the recorded exchange the evaluation at 2° converged in 16 nonlinear iterations to
cl = 0.278767, the gradient call returned alpha_deg: 0.135059 (per degree) and a
[128, 2] shape-sensitivity field summarised as {"_array": true, "shape": [128, 2],
"l2_norm": 61.73, …}, and an evaluation at 2.5° warm-started from the previous flow needed
6 iterations (cl = 0.345916).
See the design rules in the agent guide: one code path with the REST API, structured results with summarised arrays, errors that carry hints, bounded waits.
The validate → submit → result path and the hinted error are asserted end-to-end through MCP
in tests/test_service.py::test_mcp_agent_workflow; the session calls are asserted through the
REST API (which the MCP tools call) in test_session_evaluate_gradients_and_coupling; the
deployment smoke test checks that the search index and llms.txt, which the documentation
tools read, are served. The documentation tools themselves and the session tools through MCP
are not covered by an automated test.