Skip to content

@invokta/mcp

@invokta/mcp maps every engine capability to one MCP tool and keeps the official MCP SDK behind the adapter boundary. Every tool call executes through engine.invoke.

The compatibility baseline is MCP 2025-11-25; the approved TypeScript SDK is pinned to 1.30.0 in the package manifest.

Terminal window
yarn add @invokta/mcp

The package is native ESM and requires Node.js 22.20.0 or later. SDK and transport types are not part of its public API.

The package root exports:

  • serveMcpStdio and ServeMcpStdioOptions;
  • serveMcpHttp and ServeMcpHttpOptions;
  • McpHttpAuthOptions;
  • McpHttpAuthenticationRequest and McpHttpHeaderView;
  • McpHttpProtectedResourceMetadata; and
  • McpHttpServerAddress and McpHttpServerHandle.
Capability MCP tool
Engine map key name
title title
description description
Input JSON Schema inputSchema
Output JSON Schema outputSchema
readOnly readOnlyHint
destructive destructiveHint
idempotent idempotentHint
openWorld openWorldHint

Engine name and version become the MCP server identity. Omitted tool arguments default to {} before input validation.

A successful call returns the validated result as structuredContent plus a JSON text fallback. Invocation failures return isError: true with one safe JSON text error and no structuredContent. Unsafe or unknown errors fall back to:

{"code":"EXECUTION_FAILED","message":"Capability execution failed."}

An unknown tool is a protocol InvalidParams error rather than an invocation CAPABILITY_NOT_FOUND. Valid request IDs such as numeric 0 and the empty string retain their wire identity, and cancellation reaches only its matching request.

interface ServeMcpStdioOptions {
readonly principal?: Principal | null;
readonly maxReadBufferBytes?: number;
}
function serveMcpStdio<Capabilities extends CapabilityMap>(
engine: Engine<Capabilities>,
options?: ServeMcpStdioOptions,
): Promise<void>;
await serveMcpStdio(engine, {
principal: { id: "local:mcp-host" },
maxReadBufferBytes: 10 * 1024 * 1024,
});

The principal defaults to null. The read buffer defaults to 10,485,760 bytes and must be a positive safe integer. A message that keeps the buffer at the limit is accepted; crossing it closes the transport, aborts active requests, removes stream listeners, and rejects the promise.

serveMcpStdio remains pending for the protocol connection lifetime. Clean stdin end or close shuts down the server and aborts active requests. A broken stdout pipe is treated as a normal client disconnect; another output failure is reported. Standard output is reserved for protocol messages, so diagnostics belong on standard error.

interface ServeMcpHttpOptions {
readonly host?: string;
readonly port?: number;
readonly allowedHosts?: ReadonlyArray<string>;
readonly allowedOrigins?: ReadonlyArray<string>;
readonly maxRequestBodyBytes?: number;
readonly auth: McpHttpAuthOptions;
}
interface McpHttpServerHandle {
address(): { readonly host: string; readonly port: number };
close(): Promise<void>;
}
const server = await serveMcpHttp(engine, {
host: "127.0.0.1",
port: 3000,
allowedHosts: ["engine.example"],
allowedOrigins: ["https://console.example"],
maxRequestBodyBytes: 1024 * 1024,
auth: {
mode: "required",
authenticate,
},
});

Defaults are 127.0.0.1, port 3000, and a 1,048,576-byte body limit. Use port 0 in tests to let the operating system select an available port, then read it with server.address(). The address is the listener address, not a public URL.

server.close() is idempotent, shares concurrent close calls, aborts active request signals, and closes their connections.

Each accepted POST creates a fresh MCP server and transport. The adapter has no session ID, resumption, event store, server-to-client SSE, cross-request cancellation, resources, prompts, sampling, elicitation, or tasks.

The sole protocol endpoint is the exact canonical POST /mcp. When Protected Resource Metadata is configured, its generated well-known GET route is the only non-protocol route. Dot segments, percent-encoded aliases, queries, fragments, absolute-form targets, and batches never reach protocol dispatch.

Checks occur before capability execution:

  1. exactly one raw Host header and an allowed host;
  2. an optional but valid and allowed Origin;
  3. canonical route, supported method, and declared body size;
  4. exactly one unambiguous Authorization value for protected requests;
  5. authentication and principal snapshotting;
  6. exact JSON content type, required accepted response media, streamed body bound, strict UTF-8, and one JSON-RPC message; and
  7. MCP dispatch to engine.invoke.

A non-loopback bind requires a non-empty allowedHosts list. Forwarded-host headers are ignored. An absent Origin is allowed for non-browser clients; any present Origin must match a configured normalized HTTP(S) origin exactly, including scheme and any non-default port.

Accept must include both application/json and text/event-stream with positive quality. One raw Content-Type header must identify application/json. The body-size boundary is inclusive, and decoding is strict incremental UTF-8.

Status Boundary meaning
200 MCP response, including capability FORBIDDEN as a tool error
202 Accepted notification with no protocol result body
400 Invalid target/header ambiguity, JSON parsing, or top-level batch
401 Missing/invalid credentials or malformed returned Principal
403 Rejected Host or Origin
404 Unknown route
405 Unsupported method
406 Required response media types not accepted
413 Declared or streamed body crossed the configured limit
415 Missing, duplicated, or unsupported content type
500 Authentication infrastructure or internal boundary failure
type McpHttpAuthOptions =
| {
readonly mode: "required";
readonly authenticate: (
request: McpHttpAuthenticationRequest,
) => Principal | null | Promise<Principal | null>;
readonly resourceMetadata?:
McpHttpProtectedResourceMetadata;
}
| {
readonly mode:
"dangerously-disabled-for-development";
};

The authentication request contains normalized path, method, a read-only header view, and a cancellation signal. Returning a principal authenticates the request; returning null produces 401; throwing produces a sanitized 500. The adapter validates and deep-snapshots the principal before engine.invoke.

Domain authorization stays in the capability access rule. Read the HTTP authentication guide for verifier mapping, Protected Resource Metadata, proxy boundaries, and secret handling.

auth: {
mode: "dangerously-disabled-for-development",
}

This mode supplies principal = null. Keep it on an isolated loopback process; authenticated capabilities remain closed. It is not a production authentication strategy.

The adapter provides no TLS termination, proxy-trust policy, rate limit, concurrency limit, authentication retry, or verifier timeout. The host and deployment own those controls. Verifiers and capability dependencies must observe their request signals and apply their own finite outbound deadlines.

See execution channels for composition roots and scope and limits for the complete framework boundary.