Skip to content

Expose an wasmagent agent as an MCP server (F1)

The @wasmagent/mcp-server package wraps any object that runs like an agent — ToolCallingAgent, CodeAgent, or any custom SubagentRunnable — in a Model Context Protocol server. Hosts that already speak MCP (Claude Code, Cursor 2.4+, Copilot, Gemini CLI, Bedrock AgentCore, Microsoft Agent Framework) can then invoke your agent like any other MCP tool — list its capabilities, call it synchronously, or kick off long-running tasks they poll later.

Why this exists

wasmagent-js was previously a one-way MCP citizen: it consumed MCP servers through McpToolCollection, but no host could call an wasmagent agent. F1 closes the loop. The same Workers/Node deployment that runs your agent now ships its own MCP endpoint — no extra service, no protocol drift.

Quick start

ts
import { ToolCallingAgent } from "@wasmagent/core";
import {
  McpAgentServer,
  createFetchHandler,
  InMemoryTaskStore,
} from "@wasmagent/mcp-server";

const agent = new ToolCallingAgent({ /* your agent */ });

const server = new McpAgentServer({
  serverInfo: { name: "my-coding-agent", version: "1.0.0" },
  agent,
  // Default: one tool 'run_agent' that takes { task: string }.
  // You can publish multiple narrower tools by passing them explicitly.
  taskStore: new InMemoryTaskStore(), // swap for KV-backed in production
});

// Streamable HTTP — works in Cloudflare Workers, Bun.serve, Node 18+.
const handler = createFetchHandler(server, { path: "/mcp" });

// In your worker:
export default {
  async fetch(request: Request) {
    return handler(request);
  },
};

A host configures your endpoint:

jsonc
{
  "mcpServers": {
    "my-coding-agent": {
      "url": "https://my-worker.workers.dev/mcp"
    }
  }
}

After initialize, the host can call tools/list, tools/call, tasks/create, tasks/get, tasks/cancel, and tasks/respond. The underlying agent runs once per call; events stream into the persisted task record so a worker recycle never loses progress.

Methods

MethodPurposeNotes
initializeCapability handshakeReturns protocolVersion: "2025-11-25" and capabilities.tools + capabilities.tasks
tools/listList advertised tools_meta.longRunning hints route to Tasks
tools/callSynchronous tool callAuto-escalates to Tasks when syncTimeoutMs fires; the response then carries _meta.taskId
tasks/createStart a long taskReturns { id, state: "pending" }
tasks/getPoll a taskReturns the full McpTaskRecord with state, events, result, error, or pending elicitation
tasks/cancelCancel an in-flight taskSets state to failed with error: "cancelled by host"
tasks/respondReply to an elicitationRequired after the agent emits await_human_input
tasks/listEnumerate tasksOptional — depends on the store implementing list()
pingLiveness checkReturns {}

Long-running tasks (the 2025-11-25 Tasks API)

When an agent's work exceeds syncTimeoutMs (default 25 s), the synchronous tools/call response transparently escalates to the Tasks API. The host gets back { _meta: { taskId } } and polls tasks/get until state is complete, failed, or awaiting-input.

McpAgentServer keeps the run going in the background; the persisted record moves through pending → running → complete | failed | awaiting-input. Every five emitted agent events the record is flushed to the task store, so a recycle in the middle of a 5-minute task only loses ≤4 events of progress.

Stateless across restarts

The server holds no in-memory session state. Every method takes the task id as an argument; everything else is read from the configured McpTaskStore. This is the design for the 2026-07-28 Release Candidate, which removes the session-id concept entirely. To survive a worker recycle:

  1. Use a KV-backed McpTaskStore (write a 30-line adapter for your KV; the in-memory implementation in taskStore.ts is the contract).
  2. Make the wrapped agent itself recoverable — if the agent uses KvCheckpointer, the resume path already works. The MCP server treats tasks as black-box runs of agent.run(task); if that generator can be re-entered after a recycle, the server's persistence is sufficient.

Elicitation (await_human_input)

When the agent emits await_human_input, the server:

  1. Records pendingElicitation on the task and switches state to awaiting-input.
  2. Stops the run — the generator is held by the agent's checkpointing layer, NOT by the MCP server.
  3. Surfaces the prompt to the host via tasks/get (which echoes the elicitation field back).

The host calls tasks/respond with the user's reply; the server clears the pending field and flips state back to running. Resuming the agent's actual generator from that point is the responsibility of the host's CheckpointableRun plumbing — F1 deliberately does not own that path (letting it would create two competing resume mechanisms in the codebase).

Per-tool customisation

Publish more than one tool by passing the tools option:

ts
new McpAgentServer({
  serverInfo: { name: "...", version: "1.0" },
  agent,
  tools: [
    {
      name: "summarise_pr",
      description: "Summarise a GitHub PR by URL.",
      inputSchema: {
        type: "object",
        properties: { url: { type: "string" } },
        required: ["url"],
      },
      // Convert the host's structured args into the task string the agent expects.
      resolveTask: ({ url }) => `Summarise this PR: ${url}`,
    },
    {
      name: "deep_research",
      description: "Multi-source research with verification — long running.",
      inputSchema: { type: "object", properties: { question: { type: "string" } } },
      longRunning: true,
      resolveTask: ({ question }) => `Research: ${question}`,
    },
  ],
});

Tools with longRunning: true always go through tasks/create regardless of syncTimeoutMs.

Spec compliance notes

  • Targets MCP 2025-11-25 stable for over-the-wire compatibility with every shipping host.
  • Designed within 2026-07-28 RC constraints: no session-id reliance, no unsolicited server-initiated requests, elicitation only inside an active request's response.
  • JSON-RPC 2.0 envelope, batch support, the standard -32700 / -32600 / -32601 / -32602 / -32603 error codes plus MCP-extended -32010 / -32011 / -32012 for task-not-found, tool-not-found, and task-not-awaiting.

Released under the Apache-2.0 License.