Skip to main content
Alpha

MCP

@netscript/ai/mcp is the MCP client stack: it turns external Model Context Protocol servers into typed tools your agent loop can call — transports, injected auth, multi-server pooling, and a safe rendering path for the ui:// resources a tool result can carry. It is published as part of @netscript/ai@0.0.1-beta.11 and installs with the engine: deno add jsr:@netscript/ai@0.0.1-beta.11.

The story: one config instead of a protocol integration

Your agent needs a capability that lives behind an MCP server — a docs-search server, an internal ops server, a vendor's tool endpoint. Wiring that by hand is a protocol integration, not a feature: JSON-RPC framing over stdio or HTTP, session lifecycle, reconnect-with-backoff when the connection drops mid-run, auth headers that must not be hardcoded, translating the server's tool schemas into whatever your agent's tool registry expects, and deciding what to do when a result embeds UI content you did not author. Every one of those steps is a place for an agent — human or AI — to burn a turn on glue that has nothing to do with the feature.

The ./mcp subpath collapses that to configuration. You declare servers as data, register the pool into the same tool registry the agent loop already dispatches through, and the remote tools become ordinary registry entries:

import { createMcpTransportPool, registerMcpTools } from "@netscript/ai/mcp";
import { createToolRegistry } from "@netscript/ai/tools";

const pool = createMcpTransportPool({
  servers: [{
    kind: "streamable-http",
    serverId: "search",
    url: "https://mcp.example.com",
    auth: { mode: "api-token", token: Deno.env.get("MCP_TOKEN")!, scheme: "Bearer" },
  }],
});

const registry = createToolRegistry();
const registration = await registerMcpTools(registry, pool);
// registration.toolNames — the remote tools, now dispatchable like any local tool
// registration.stop()   — detach them again

From here the agent loop treats a remote MCP tool exactly like a local defineAiTool definition — same registry, same dispatch, same AgentChunk stream on the wire.

Transports — stdio and reconnectable Streamable-HTTP

createMcpTransport(config) takes a discriminated config — { kind: "stdio" } or { kind: "streamable-http" } — and returns an McpTransportPort. The Streamable-HTTP transport is built for long-lived agent sessions: it reconnects with configurable backoff (McpBackoffConfig) and exposes its lifecycle as an observable state machine.

Transport lifecycle — @netscript/ai/mcp
NameTypeDescription
McpConnectionState "disconnected" | "connecting" | "connected" | "reconnecting" | "closed" The transport lifecycle union; reconnecting is a first-class state, not an error.
onStateChange(handler) (state, previous) => void Subscribe to lifecycle transitions (McpStateChangeHandler); returns an unsubscribe function.
StreamableHttpMcpTransport class The reconnectable Streamable-HTTP transport; configured via StreamableHttpMcpTransportConfig (serverId, url, auth, backoff).
StdioMcpTransport class The stdio transport for local process-based MCP servers.

Auth is injected at the composition root, never baked into a transport class. McpAuthConfig is a three-mode union — { mode: "none" }, { mode: "api-token", token, headerName?, scheme? }, or { mode: "oauth", accessToken, tokenType? } — so the same server declaration works across environments with only the secret changing.

The pool — many servers, one tool namespace

Real agents talk to more than one server. createMcpTransportPool(config) builds a multi-server pool from serializable transport configs (so server declarations can live in configuration, not code); createMcpTransportPoolFromTransports wraps transports you already own. The pool is itself an McpTransportPort, so everything that accepts one transport accepts a pool of them.

McpTransportPool — the multi-server surface
NameTypeDescription
connect(options) Promise Open every pooled transport and return the discovered tools, name-prefixed by server id so two servers' tools never collide.
listTools(options) Promise Re-list tools from all pooled transports without tearing down warm connections.
callTool(name, args, options) Promise Route a prefixed tool name to its server and extract any ui:// resources from the result.
state / onStateChange McpConnectionState The aggregate lifecycle across all pooled transports.
server(serverId) / serverIds McpTransportPort | undefined Reach a single pooled transport when you need it.
stop() Promise Stop every pooled transport and clear discovered tool routes.

registerMcpTools(registry, transport) is the bridge into the agent: it surfaces the remote tools into a ToolRegistryPort and returns an McpToolRegistration whose .stop() detaches them — registration is reversible, not a global mutation.

ui:// resources — tool results that carry UI, rendered safely

An MCP tool result can embed ui:// resources — UI content authored by the server, not by you. The pool surfaces these on every McpPooledToolResult as uiResources, and extractMcpUiResources(result) pulls the data-only McpUiResource records from any raw tool result. The engine stops there by design: it extracts, it never renders.

Rendering belongs to the web layer, behind two guardrails you install with netscript ui:add ai:

  • the McpUiWidget island (from the @netscript/fresh-ui copy registry) renders a ui:// resource in a sandboxed frame — sanitized, themed through the sandbox route, no-referrer;
  • the createMcpSandboxHandler route (on @netscript/fresh/ai/sandbox) serves the resource with the active theme's tokens and a per-response CSP — see the durable-chat sandbox section for the handler itself.

This is the same trust boundary as the render_ui tool: model- or server-authored UI reaches the page only through a curated renderer or a sandbox, never as raw HTML in your island tree. The whole path is exercised by the framework's own merge gate — the CLI E2E suite drives a real Streamable-HTTP MCP round trip through this client stack into a rendered McpUiWidget before a branch can land.

One comparison

Encore's MCP story points inward: it ships an MCP server that exposes a running Encore backend's introspection surface to coding agents, so an agent can inspect and verify the backend it is editing. NetScript ships both halves. The inward half is the netscript agent mcp server (documented under Agent tooling and the @netscript/mcp reference): a stdio server that gives a coding agent framework-aware diagnostics, telemetry summaries, and doc search over the project it is editing. The outward half — this page — is the @netscript/ai/mcp client stack for consuming external MCP servers as tools inside your own product's agents, with a sandboxed path for the UI those tools return. Both paths are published and gate-tested.

Where to go next