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.
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:
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; isErroristrue;- the result has any other top-level key, or is larger than 1 MiB;
callToolthrows, 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
defineMcpTooleach. There is no way to expose a whole server at once. - An MCP server is someone else's code. Its results pass your
outputschema 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.