Skip to content

Connecting to MCP Servers ​

An agent's own tools are the capabilities you generate and own. MCP lets the same agent reach out at runtime to capabilities it doesn't own - a filesystem server, a GitHub server, an internal service - and call them like any other tool. You declare that under spec.agent.mcp.

What MCP is, and why an agent connects out ​

MCP (Model Context Protocol) is an open protocol for exposing tools and context from a server to an LLM client. ADL's generated agent ships with a built-in MCP client - today only for Go agents, see What the reference consumer wires - point it at one or more MCP servers and their tools become available to the model alongside your spec.tools.

The two are complementary:

  • spec.tools are deterministic entrypoints you define and generate into the project. You own the code.
  • MCP servers are external capabilities discovered at runtime. You don't own them - you connect to them.

An agent can use either or both. Reach for MCP when the capability already exists as a server (or someone else maintains it) and you'd rather connect than reimplement.

mcp lives under spec.agent - not at the top of spec - because it only makes sense once an LLM is driving the agent. It is the block that bridges an A2A agent to the MCP ecosystem.

The two sides: which servers, and how ​

The mcp block has two halves:

  • mcp.servers - which servers to connect to.
  • The surrounding client knobs - how to connect: the enable toggle, timeouts, refresh interval, and retry/backoff. These apply globally across every server (the built-in client is HTTP-single-endpoint, so there is no per-server override).

The client is disabled by default. enabled is required whenever the mcp block is present, so turning it on is always explicit - omit the block or set enabled: false and no MCP client is generated or wired in, even if servers lists servers.

Every client knob maps 1:1 to an A2A_MCP_* environment variable. The value in the manifest becomes the default the generated project emits; the matching environment variable overrides it at runtime. The defaults are listed in a generated .env.example only when spec.development.sandbox.dockerCompose.enabled: true - that flag is what makes adl-cli write the file.

FieldEnv varDefaultWhat it controls
enabledA2A_MCP_ENABLEDfalseMaster switch - no client generated if off.
endpointA2A_MCP_ENDPOINT/mcpPath appended to each server URL.
refreshIntervalA2A_MCP_REFRESH_INTERVAL5mHow often tools are re-discovered.
dialTimeoutA2A_MCP_DIAL_TIMEOUT30sConnection timeout.
callTimeoutA2A_MCP_CALL_TIMEOUT30sSingle tool-call timeout.
maxRetriesA2A_MCP_MAX_RETRIES0Retries on failure (0 = retry forever).
retryIntervalA2A_MCP_RETRY_INTERVAL2sInitial backoff between retries.
retryMaxIntervalA2A_MCP_RETRY_MAX_INTERVAL30sCeiling for the backoff.

Interval and timeout fields are Go duration strings (5m, 30s, 1h30m). Every knob shown above is optional - set enabled: true plus servers and you get the defaults. See Reference: agent#mcp for the full table.

The three transports ​

Each entry in mcp.servers needs a name and a transport; the rest are the connection details for that transport:

  • stdio - launch a local subprocess (command + args, with optional env) and talk to it over stdin/stdout.
  • http - reach a remote endpoint by url, with optional headers (e.g. an Authorization token).
  • sse - a remote endpoint over Server-Sent Events, also addressed by url + headers.
yaml
spec:
  agent:
    provider: openai
    model: gpt-4.1
    mcp:
      enabled: true
      servers:
        - name: github # http: remote endpoint
          transport: http
          url: https://mcp.example.com/github
          headers:
            Authorization: Bearer ${GITHUB_MCP_TOKEN}
        - name: search # http: another remote endpoint
          transport: http
          url: https://mcp.example.com/search

Only name and transport are required; the schema does not enforce which fields accompany a given transport, so consumers stay lenient. Placeholders like ${GITHUB_MCP_TOKEN} are resolved by the consumer, not the schema - see Secrets & interpolation for where credentials come from.

What the reference consumer wires ​

What the schema accepts and what a consumer connects to are two different things. All three transports are valid manifest input, but adl-cli - the reference consumer - currently wires only http servers, and only for Go agents:

  • http servers are wired. Their base URLs become A2A_MCP_SERVERS in the generated project.
  • stdio and sse servers validate, then are dropped. adl-cli emits a warning ("the ADK MCP client is streamable-HTTP-only") and leaves them out of A2A_MCP_SERVERS, so a stdio subprocess is never launched. An agent whose mcp block declares no http server gets an empty A2A_MCP_SERVERS and fails to start until it is set from the environment.
  • Go only. For typescript and rust agents the whole spec.agent.mcp block is ignored with a warning - no MCP client is generated.

stdio and sse stay in the schema because the manifest describes intent and other consumers may support them. Author against http if you want the generated Go agent to connect today.

Next steps ​