MCP Connect MCP servers as Ax tools for generation, agents, prompts, resources, and runtime workflows. typescript concepts concepts/mcp website/content-src/templates/concept-mcp.md concepts MCP

MCP

Model Context Protocol is the clean boundary between Ax programs and external tool servers. An MCP server can expose tools, prompts, resources, and resource templates. Ax connects to the server, negotiates the protocol version, lists the available capabilities, and turns them into normal Ax functions.

That means MCP does not need a special prompt path. Once connected, MCP tools can be passed to ax() generation, grouped for agent() discovery, traced like other tools, and optimized as part of the same program behavior.

MCP bridge

Mental Model

flowchart LR
  App["App code"] --> Client["AxMCPClient"]
  Client --> Transport["stdio / HTTP / SSE transport"]
  Transport --> Server["MCP server"]
  Server --> Capabilities["tools, prompts, resources"]
  Capabilities --> Functions["Ax functions"]
  Functions --> Program["ax() or agent()"]

MCP tools become typed host functions. Prompts get a prompt_ prefix, resources get a resource_ prefix, and resource templates become callable lookups. Function overrides let you rename or clarify server-provided names without editing the server.

Local Stdio Servers

Use stdio for local MCP servers started as child processes. This is the common path for local memory, filesystem, or desktop-style tools.

TypeScript
import { AxMCPClient } from '@ax-llm/ax';
import { AxMCPStdioTransport } from '@ax-llm/ax-tools';

const transport = new AxMCPStdioTransport({
  command: 'npx',
  args: ['-y', '@modelcontextprotocol/server-memory'],
});

const mcpClient = new AxMCPClient(transport, { debug: false });
await mcpClient.init();

Streamable HTTP Servers

Use streamable HTTP for remote services. This keeps Ax on the MCP wire protocol while the transport owns HTTP headers, session IDs, protocol headers, and provider-specific auth details.

TypeScript
import { AxMCPClient, AxMCPStreamableHTTPTransport } from '@ax-llm/ax';

const transport = new AxMCPStreamableHTTPTransport(
  'https://mcp.linear.app/mcp',
  { authorization: `Bearer ${process.env.LINEAR_MCP_TOKEN}` }
);

const mcpClient = new AxMCPClient(transport);
await mcpClient.init();

OAuth-Capable Remote MCP

Remote MCP services can require OAuth. Keep client credentials in environment variables, use a real redirect endpoint in production, and persist tokens through the transport token store when the package surface supports it.

TypeScript
import { AxMCPClient, AxMCPStreamableHTTPTransport } from '@ax-llm/ax';

const transport = new AxMCPStreamableHTTPTransport('https://mcp.notion.com/mcp', {
  oauth: {
    clientId: process.env.MCP_OAUTH_CLIENT_ID,
    clientSecret: process.env.MCP_OAUTH_CLIENT_SECRET,
    redirectUri: process.env.MCP_OAUTH_REDIRECT_URI ?? 'http://localhost:8787/callback',
    scopes: process.env.MCP_OAUTH_SCOPES?.split(','),
  },
});

const mcpClient = new AxMCPClient(transport);
await mcpClient.init();

No-Key Scripted Tests

Scripted transports are useful for deterministic examples, conformance, and docs. They exercise initialize, capability discovery, tool listing, tool calls, and toFunction() without hitting a network or needing provider credentials.

TypeScript
import { AxMCPClient, type AxMCPTransport } from '@ax-llm/ax';

const transport: AxMCPTransport = createScriptedWeatherTransport();
const client = new AxMCPClient(transport);
await client.init();

const weather = client.toFunction().find((fn) => fn.name === 'lookup_weather');
const result = await weather?.func({ city: 'Vancouver' });

Capabilities And Overrides

Capabilities tell you what the server exposed after initialization. Use them for diagnostics and for deciding whether the current server is usable for a workflow.

TypeScript
const caps = mcpClient.getCapabilities();
const functions = mcpClient.toFunction();

console.log(caps.tools, caps.prompts, caps.resources);
console.log(functions.map((fn) => fn.name));

Overrides let the application rename awkward MCP tool names, improve descriptions, or make a server fit an agent namespace without changing the MCP server.

TypeScript
const mcpClient = new AxMCPClient(transport, {
  functionOverrides: [
    {
      name: 'search_documents',
      updates: { name: 'findDocs', description: 'Search internal docs' },
    },
  ],
});

Use MCP With ax()

For direct structured generation, initialize the client and pass its functions to the program run. This is best when the task is one typed request and the model only needs a small tool set.

TypeScript
import { AxMCPClient, ax } from '@ax-llm/ax';

const mcpClient = new AxMCPClient(transport);
await mcpClient.init();

const answer = ax('question:string -> answer:string');
const out = await answer.forward(
  llm,
  { question: 'What did we store about the release plan?' },
  { functions: mcpClient.toFunction() }
);

Use MCP With agent()

Agents can receive MCP tools directly or through grouped discovery modules. Use flat functions when the tool list is small. Use groups when a server exposes many tools and the actor should discover the right module before seeing every schema.

TypeScript
import { AxJSRuntime, AxMCPClient, agent } from '@ax-llm/ax';

const mcpClient = new AxMCPClient(transport);
await mcpClient.init();

const assistant = agent('request:string -> response:string', {
  functions: [mcpClient],
  functionDiscovery: true,
  contextFields: [],
  runtime: new AxJSRuntime(),
});
TypeScript
const assistant = agent('request:string -> response:string', {
  functions: [
    {
      namespace: 'memory',
      title: 'Memory MCP',
      description: 'Persistent memory tools',
      selectionCriteria: 'Use for memory lookup and updates.',
      functions: [mcpClient],
    },
  ],
  functionDiscovery: true,
  contextFields: [],
});

Production Notes

  • Keep remote MCP endpoints on an allowlist and use SSRF protection for user-supplied URLs.
  • Prefer narrow namespaces and clear selection criteria so small models can choose tools correctly.
  • Treat MCP tools as side-effect boundaries: validate inputs, log calls, and make destructive operations explicit.
  • Refresh capabilities when the server sends list-changed notifications.
  • Trace MCP initialize, list, call, retry, error, and token-refresh behavior alongside model usage.
  • Keep scripted MCP examples in the docs and test suite so package conformance does not depend on external servers.

See Tools, ax() generation, agent() agents, and agent() API.

Docs