Connect MCP servers¶
Model Context Protocol servers appear as
additional tool providers. Once configured, their tools are called exactly like
local ones — await self.call_tool(...) — and recorded in provenance the same
way.
Install the client¶
Stdio: a server launched as a subprocess¶
The command is split with shell-like tokenisation and launched as a
subprocess; Science ADK speaks JSON-RPC over its stdin and stdout. The section
name (pubchem) becomes the provider prefix.
pubchem.get_compound_by_name(name: str) -> object
pubchem.search_compounds(query: str, limit: integer = 10) -> object
local.power_law_fit(x: list, y: list) -> dict
HTTP: a remote server¶
[tools.remote_compute]
url = "https://mcp.example.org/api"
token_env = "COMPUTE_API_TOKEN"
token_env names the environment variable holding the credential. The
secret itself never goes into science.toml, which is a file you commit.
If the variable is unset, the registry refuses to build the provider rather than silently running without authentication:
ToolError: Tool provider 'remote_compute' needs COMPUTE_API_TOKEN environment
variable, which is not set.
The token is sent as a bearer token on each request.
Using the tools¶
class FetchCompound(DataAgent):
source_kind: ClassVar[str] = "measured"
ports: Ports = Ports(
inputs=[Port("settings", "json")],
outputs=[Port("compound", "json")],
)
async def execute(self):
settings = await self.input("settings")
compound = await self.call_tool(
"pubchem.get_compound_by_name", name=settings["name"]
)
await self.log(f"retrieved {settings['name']} from PubChem")
return {"compound": compound}
Declare it on the node, as always:
Qualify names across providers
A bare name resolves only if it is unambiguous. As soon as more than one
provider is configured, prefer provider.tool — it documents where the
data came from at the call site.
When a server is unavailable¶
The registry warns and keeps going rather than failing the whole project:
warning: tool provider 'pubchem' unavailable: Cannot reach MCP server
'pubchem' at https://...: [Errno -2] Name or service not known
The run then fails where the tool was actually needed, and the node that
declared it fails tool_use_verified. That is the intended behaviour: a
missing data source must never quietly become a run with fewer inputs.
Provenance is identical¶
An MCP tool call is recorded exactly like a local one:
A remote dependency does not weaken the integrity guarantees; it just widens what the run depended on, which the trace now says out loud.
Reproducibility caveats¶
A remote server can change under you in a way a local function cannot. Two habits help:
- Record the response, not just the conclusion. Let the data node output
the retrieved payload so it lands in the trace (or spills to
data/), and keep the derivation in a downstream node. - Pin the server version where the protocol allows it, and say which
version you used in
HYPOTHESIS.md.
Checklist¶
science-adk tools # the server's tools appear, correctly prefixed
science-adk validate # the DAG still checks out
science-adk run --only fetch # the call goes through and is recorded
jq '.nodes[] | select(.node_id=="fetch") | .provenance' research/*/runs/*/trace.json