MCP Bridging

metadata:
  mcp:
    filesystem:
      command: npx
      args: [-y, "@modelcontextprotocol/server-filesystem", ./data]
    github:
      command: npx
      args: [-y, "@modelcontextprotocol/server-github"]
      env: { GITHUB_TOKEN: "\${GITHUB_TOKEN}" }
    remote-tools:
      url: https://mcp.example.com/tools

Each MCP server entry supports two transport modes. When command is specified, the framework launches a child process and communicates over stdio (StdioClientTransport). When url is specified, the framework connects over Streamable HTTP (StreamableHTTPClientTransport). If both are present, url takes precedence.

Environment variable references in MCP env values that should be resolved at server launch time (not during metadata interpolation) must be escaped with a backslash: \${GITHUB_TOKEN}. Without the backslash, the framework's string interpolation would resolve ${GITHUB_TOKEN} via pathGet(ctx, "GITHUB_TOKEN"), which returns undefined.

MCP servers are initialized lazily by mcp middleware on first invocation, with a per-server guard to avoid re-initialization. MCP tools are registered with prefixed names (read_file from filesystemfilesystem-read-file). Tool names are sanitized to valid Agent Apps names: lowercased, non-alphanumeric characters replaced with hyphens, consecutive hyphens collapsed, leading/trailing hyphens stripped. Source URI: mcp://filesystem/read-file. MCP tools are public (registered with params).

MCP Elicitation

When an external MCP server elicits input during a tool call, the framework routes the elicitation through the calling context's channel. This uses AsyncLocalStorage for call-time scope correlation:

  1. When a skill invokes an MCP tool, the tool's fn wraps callTool in mcpScope.run({ nonlocals: ctx.nonlocals }).
  2. If the external server sends an elicitation request during that call, the handler reads mcpScope.getStore() to recover the caller's nonlocals.
  3. The handler invokes gateway-receive via globals.runtime.invoke, passing the caller's scope. This routes the elicitation to the correct transport (email, Slack, CLI, etc.).
  4. If no store exists (elicitation outside a tool call), the handler falls through to defaults.

This ensures that an MCP tool called from within an email-dispatched pipeline prompts the user via email, not CLI. The correlation is per-call and concurrency-safe.

Ask AI