Skip to content
Mayura

Agents and models

MCP tools

Wrap one operation of a Model Context Protocol server as a Mayura tool, with the effects, permissions and cost you declare.

The Model Context Protocol (MCP) is a standard way for servers to offer tools to AI applications. mayura/adapter-mcp turns one tool of an MCP server into an ordinary Mayura tool, so an agent can call it under the same permission, validation, guard, budget and timeout checks as your own tools.

Mayura does not connect to MCP servers itself, discover their tools or read their descriptions of what a tool does. You connect with an MCP client of your choice, pick each remote tool you want, and declare what it is allowed to do.

ts
import { createRuntime, defineAgent, z } from 'mayura';
import { defineMcpTool } from 'mayura/adapter-mcp';

const IssueInput = z.object({ title: z.string().max(200), body: z.string().max(10_000) });
const IssueOutput = z.object({ number: z.number().int(), url: z.string() });

const createIssue = defineMcpTool({
  id: 'tracker.create_issue',
  version: '1',
  description: 'Create an issue in the team tracker.',
  remoteName: 'create_issue',  // the tool's name on the MCP server
  input: IssueInput,
  output: IssueOutput,
  effects: 'write',
  capabilities: ['tracker:write'],
  timeoutMs: 15_000,
  client: {
    // `mcp` is a connected client from an MCP SDK, for example the official TypeScript SDK's Client.
    callTool: ({ name, arguments: args, signal }) => mcp.callTool({ name, arguments: args }, undefined, { signal }),
  },
});

const agent = defineAgent({
  id: 'triage', version: '1', instructions: 'File an issue for each bug report.',
  model, tools: [createIssue], input, output,
});

const runtime = createRuntime({
  profile: 'ephemeral',
  permissions: { allow: ['model:openai.responses', 'tool:tracker.create_issue', 'tracker:write', 'effect:write'] },
  limits: { maxCostMicros: 100_000 },
});

What you declare

defineMcpTool takes the same options as defineTool, minus execute, plus remoteName and client:

Option Meaning
id, version, description The Mayura tool. The model reads description, so write your own; the server's is not used.
remoteName The tool's name on the MCP server (letters, digits and ._/-, up to 128 characters).
input, output Validators for the arguments and the result. The input must be a JSON object.
inputJsonSchema The input the model sees. Generated from input when the validator can describe itself (Zod 4.2 and later).
effects none, read, write or host: what calling this tool can change.
capabilities Extra permission strings the runtime must grant, such as tracker:write.
client An object with a callTool(request) method (below).
timeoutMs, costMicros Deadline (default 30,000) and the most one call may cost (default 0).

Nothing is taken from the server: not the effects, not the cost, not the schema. Declare them from what you know the remote tool does. To call a tool, the runtime must grant tool:<id>, every capability, and effect:<effects> unless the effects are none. See Permissions.

The client

client is any object with a callTool method. Mayura calls it with a frozen request:

ts
import type { McpClient } from 'mayura/adapter-mcp';

const client: McpClient = {
  async callTool({ name, arguments: args, signal }) {
    // Send an MCP tools/call request for `name` with `args`, and stop when `signal` aborts.
    return await mcp.callTool({ name, arguments: args }, undefined, { signal });
  },
};

Connecting, authenticating and closing the MCP session are up to you and your MCP client. Mayura only calls callTool, and only after the runtime's checks allowed the call and the input passed its validator.

What the server must return

callTool must resolve to the MCP tool result: an object with structuredContent, and optionally content, isError and _meta. Mayura validates structuredContent with your output schema and hands that to the agent.

The call fails when:

  • the result has no structuredContent (text-only results are not parsed into data); MCP tools that declare an output schema return structured content;
  • isError is true;
  • the result has any other top-level key, or is larger than 1 MiB;
  • callTool throws, or the timeout passes.

Error details from the server or the transport never reach the model, events or outcomes. _meta is accepted and dropped.

When a call fails

For a tool with effects other than none, a failure after the request was sent means Mayura cannot know whether the server acted. The run ends outcome_unknown, and you should check the remote system before retrying. This includes a result with isError: true, because the server may have done part of the work. For a tool with effects none, the run ends failed. See Outcomes.

Good to know

  • Wrap only the remote tools the agent needs, one defineMcpTool each. There is no way to expose a whole server at once.
  • An MCP server is someone else's code. Its results pass your output schema and the agent's output guards before the model sees them; add guardrails if a server may return text you would not show a model.
  • Server-side tool lists and descriptions can change. Because you declare each tool, a change on the server does not change what your agent is allowed to do.