Skip to content

Proposal: native MCP protocol primitives in the standard library #7185

Open

Description

@fy2ne

MCP (Model Context Protocol) is becoming a standard for AI tool integration. Deno could be the most natural platform for building MCP servers by adding native protocol primitives to the standard library. Currently MCP server authors using Deno must either use the npm package @modelcontextprotocol/sdk (Node.js-focused) or implement JSON-RPC manually over std/http. A Deno-native std/mcp module would provide proper TypeScript types for the MCP protocol, handle JSON-RPC over stdin/stdout and HTTP transports, integrate with Deno's permission model, and require zero configuration. MCP servers are often single-file scripts which maps perfectly to Deno's design. Deno's security model (--allow-read, --allow-net) also maps cleanly to MCP's tool permission concept.

Activity

  1. transferred this issue fromdenoland/denoon Jun 17, 2026
  2. luk3skyw4lker commented on Jul 2, 2026

    @luk3skyw4lker
    Contributor

    I will look into this if nobody else is into it right now

  3. fy2ne commented on Jul 2, 2026

    @fy2ne
    Author

    Hey @luk3skyw4lker, thanks for reaching out!

    I've just set up my fork to start mapping out the baseline directory structure and core JSON-RPC types. Since you're an active contributor here, I'm completely open to how we tackle this—whether you'd prefer to take the lead, or if we want to team up on a draft PR to align on Deno's stream model for the Stdio/SSE transports.

    Let me know what works best for you and your availability!

  4. luk3skyw4lker commented on Jul 2, 2026

    @luk3skyw4lker
    Contributor

    Happy to hear that @fy2ne!

    I'd be happy to take the lead and highlight a plan in here so we can discuss strenghts and flaws of it, what do you think? I can probably set up the plan by later today and paste it here as a markdown doc so we can talk about it

  5. fy2ne commented on Jul 2, 2026

    @fy2ne
    Author

    That sounds like a perfect plan, looking forward to reviewing the markdown breakdown.

    Once we align on the architecture and core design, I’m more than happy to split the implementation workload with you whether that's mapping out the schema types or building out the initial transport streams.

    Take your time, and see you back here later today!

  6. luk3skyw4lker commented on Jul 3, 2026

    @luk3skyw4lker
    Contributor

    So I have made the plan in an RFC format and will paste it in the next comment so you can let me know what you think @fy2ne

    I've thought about the split of work and if it's not a big deal, I'd like to take the big chunk in the bootstrap and the server part and you could do the client one, but I'm also open to suggestions once you see the plan, not trying to push you off, just got a little bit of free time and it's been long since I've done my last big contribution lolz

  7. luk3skyw4lker commented on Jul 3, 2026

    @luk3skyw4lker
    Contributor

    RFC: @std/mcp — MCP primitives for the Deno Standard Library

    Summary

    This proposal introduces a new @std/mcp package to the Deno Standard Library.
    The package provides:

    • TypeScript types for the
      Model Context Protocol (MCP) specification
    • JSON-RPC 2.0 framing and dispatch helpers
    • Stdio and Streamable HTTP transports
    • High-level server and client APIs

    The design targets zero configuration and native integration with Deno's
    permission model, making it straightforward to ship MCP servers as single-file
    Deno scripts.

    Motivation

    MCP has become a common way to connect LLM applications with external tools,
    resources, and prompts. In practice, many MCP servers are small, single-purpose
    programs — often a single script that exposes one or a few tools. This maps
    directly onto Deno's strengths:

    MCP server reality Deno capability
    Single-file scripts deno run server.ts with no build step
    TypeScript-first ecosystem Native TypeScript, no transpiler config
    Sandboxed tool execution Explicit --allow-* permission flags
    Stdio transport for local hosts First-class Deno.stdin / Deno.stdout
    HTTP transport for remote hosts @std/http already in std

    Today, MCP SDKs exist primarily for Node.js and Python. Deno users must either
    reach for npm compatibility layers or reimplement protocol details by hand. A
    first-party std package would give Deno a canonical, well-tested foundation for
    building MCP servers and clients.

    The value proposition in one line:

    Write an MCP server in a single TypeScript file and run it with
    deno run --allow-read server.ts — no package.json, no bundler, no config
    files.

    Why this fits deno_std

    Composes existing packages

    The MCP wire format is JSON-RPC 2.0 over newline-delimited JSON (stdio) or
    Streamable HTTP. These primitives are already well-served by existing std
    packages:

    @std/mcp should compose these packages rather than reinvent them.

    Precedent in std

    Std already ships protocol and serialization packages:

    • @std/json, @std/jsonc, @std/msgpack — data formats
    • @std/http, @std/net — network protocols

    MCP is an application protocol built on JSON-RPC. A dedicated package follows
    the same pattern as @std/http (HTTP primitives) and @std/msgpack
    (MessagePack encode/decode).

    Architecture alignment

    Per the architecture guide:

    • One top-level folder per package (mcp/)
    • mod.ts as the entry point with module documentation
    • Snake-cased source files (json_rpc.ts, stdio_transport.ts)
    • One _test.ts per source file
    • Minimal exports per file
    • Granular exports map in deno.json

    Proposed scope

    The package is organized in layers. Each layer is independently useful and
    testable.

    1. Protocol types (types.ts)

    Full TypeScript types for:

    • JSON-RPC 2.0 envelopes (request, response, notification, error)
    • MCP lifecycle messages (initialize, initialized)
    • Tool messages (tools/list, tools/call)
    • Resource messages (resources/list, resources/read,
      resources/templates/list)
    • Prompt messages (prompts/list, prompts/get)
    • Notifications (notifications/tools/list_changed, etc.)
    • Capability and metadata types

    Types mirror the official MCP schema.ts. This module is browser-compatible
    and has no runtime dependencies.

    2. JSON-RPC core (json_rpc.ts)

    Transport-agnostic framing and dispatch helpers:

    • Build and parse JSON-RPC messages
    • Correlate request/response by id
    • Standard error codes (-32700 parse error, -32600 invalid request, etc.)
    • Optional batch handling
    • A lightweight JsonRpcPeer for sending requests and dispatching incoming
      messages to handlers

    3. Transports

    A common McpTransport interface with two implementations:

    StdioTransport (stdio_transport.ts)

    • Newline-delimited JSON over Deno.stdin / Deno.stdout
    • Logging and diagnostics go to stderr (stdout is reserved for protocol per
      spec)
    • Composes @std/json parse/stringify streams and @std/streams

    HttpTransport (http_transport.ts)

    • Streamable HTTP against a single /mcp endpoint
    • Built on @std/http
    • Supports application/json (single response) and text/event-stream (SSE
      stream) response modes
    • Handles required MCP HTTP headers (MCP-Protocol-Version, Accept, etc.)

    4. Server (server.ts)

    High-level McpServer API:

    • Register tools, resources, and prompts with ergonomic helpers
    • Auto-handle initialize and capability negotiation
    • connect(transport) to start serving

    5. Client (client.ts)

    High-level McpClient API:

    • Connect to a server over stdio or HTTP
    • listTools(), callTool(), listResources(), readResource(),
      listPrompts(), getPrompt()
    • Handle server-initiated notifications

    Zero configuration

    The intended developer experience for a minimal MCP server:

    import { McpServer, StdioTransport } from "@std/mcp";
    
    const server = new McpServer({ name: "hello", version: "1.0.0" });
    
    server.tool({
      name: "greet",
      description: "Greet someone by name",
      inputSchema: {
        type: "object",
        properties: { name: { type: "string" } },
        required: ["name"],
      },
      handler: async ({ name }) => ({
        content: [{ type: "text", text: `Hello, ${name}!` }],
      }),
    });
    
    await server.connect(new StdioTransport());

    Run with:

    deno run --allow-read server.ts

    No package.json, no tsconfig.json, no bundler, no transport configuration
    beyond choosing StdioTransport or HttpTransport.

    For HTTP:

    import { HttpTransport, McpServer } from "@std/mcp";
    
    const server = new McpServer({ name: "hello", version: "1.0.0" });
    // ... register tools ...
    
    await server.connect(new HttpTransport({ port: 3000 }));
    deno run --allow-net server.ts

    Deno permission model integration

    This is the primary differentiator from Node-based MCP SDKs.

    Deno's --allow-* flags are the server's security boundary. An MCP tool that
    reads the filesystem should require --allow-read. A tool that calls an
    external API should require --allow-net. The runtime enforces this at the OS
    level — not at the application layer.

    The proposal includes an optional permission helper (either in core or a
    sub-path — see Open questions):

    import { McpServer, requirePermissions, StdioTransport } from "@std/mcp";
    
    const server = new McpServer({ name: "fs-tools", version: "1.0.0" });
    
    server.tool({
      name: "read_file",
      description: "Read a file from disk",
      permissions: ["read"], // declarative hint
      inputSchema: {
        type: "object",
        properties: { path: { type: "string" } },
        required: ["path"],
      },
      handler: async ({ path }) => {
        await requirePermissions({ read: true });
        const text = await Deno.readTextFile(path);
        return { content: [{ type: "text", text }] };
      },
    });

    requirePermissions() would use Deno.permissions.query() and
    Deno.permissions.request() to check or prompt for the needed capabilities
    before the handler runs. This stays additive and optional so the type layer
    remains runtime-neutral and browser-compatible.

    Architecture

    flowchart LR
      subgraph shared [Shared primitives]
        types[types.ts JSON-RPC and MCP types]
        rpc[json_rpc.ts framing and dispatch]
      end
      subgraph transports [Transports]
        stdio[StdioTransport stdin and stdout]
        http[HttpTransport Streamable HTTP]
      end
      server[McpServer tool resource prompt] --> rpc
      client[McpClient callTool readResource] --> rpc
      rpc --> types
      server --> transports
      client --> transports
      stdio -->|composes| stdlib1[std json streams io]
      http -->|composes| stdlib2[std http]
    
    Loading

    Message flow (stdio server)

    sequenceDiagram
      participant Host as MCP Host
      participant Stdio as StdioTransport
      participant Rpc as JsonRpcPeer
      participant Srv as McpServer
    
      Host->>Stdio: JSON-RPC initialize request
      Stdio->>Rpc: parse newline-delimited JSON
      Rpc->>Srv: dispatch initialize
      Srv->>Rpc: initialize result
      Rpc->>Stdio: serialize response
      Stdio->>Host: JSON-RPC response
    
      Host->>Stdio: tools/call request
      Stdio->>Rpc: parse message
      Rpc->>Srv: dispatch tools/call
      Srv->>Srv: run tool handler
      Srv->>Rpc: CallToolResult
      Rpc->>Stdio: serialize response
      Stdio->>Host: JSON-RPC response
    
    Loading

    API sketches

    All public APIs ship as unstable (@experimental + UNSTABLE warning)
    until vetted by the community and core team.

    types.ts

    /** JSON-RPC 2.0 request. */
    export interface JsonRpcRequest {
      jsonrpc: "2.0";
      id: string | number;
      method: string;
      params?: Record<string, unknown>;
    }
    
    /** JSON-RPC 2.0 notification (no id). */
    export interface JsonRpcNotification {
      jsonrpc: "2.0";
      method: string;
      params?: Record<string, unknown>;
    }
    
    /** JSON-RPC 2.0 success response. */
    export interface JsonRpcResponse {
      jsonrpc: "2.0";
      id: string | number;
      result: unknown;
    }
    
    /** JSON-RPC 2.0 error response. */
    export interface JsonRpcErrorResponse {
      jsonrpc: "2.0";
      id: string | number | null;
      error: JsonRpcError;
    }
    
    export interface JsonRpcError {
      code: number;
      message: string;
      data?: unknown;
    }
    
    /** Discriminated union of all JSON-RPC message shapes. */
    export type JsonRpcMessage =
      | JsonRpcRequest
      | JsonRpcNotification
      | JsonRpcResponse
      | JsonRpcErrorResponse;
    
    /** MCP protocol version string. */
    export type ProtocolVersion =
      | "2024-11-05"
      | "2025-03-26"
      | "2025-06-18"
      | "2025-11-25";
    
    export interface InitializeParams {
      protocolVersion: ProtocolVersion;
      capabilities: ClientCapabilities;
      clientInfo: Implementation;
    }
    
    export interface InitializeResult {
      protocolVersion: ProtocolVersion;
      capabilities: ServerCapabilities;
      serverInfo: Implementation;
      instructions?: string;
    }
    
    export interface Tool {
      name: string;
      description?: string;
      inputSchema: Record<string, unknown>;
    }
    
    export interface CallToolParams {
      name: string;
      arguments?: Record<string, unknown>;
    }
    
    export interface CallToolResult {
      content: ContentBlock[];
      isError?: boolean;
    }
    
    export type ContentBlock =
      | { type: "text"; text: string }
      | { type: "image"; data: string; mimeType: string }
      | { type: "resource"; resource: ResourceContents };
    
    // ... additional types for resources, prompts, capabilities, notifications

    json_rpc.ts

    import type { JsonRpcError, JsonRpcMessage, JsonRpcRequest } from "./types.ts";
    
    /** Standard JSON-RPC error codes. */
    export const PARSE_ERROR = -32700;
    export const INVALID_REQUEST = -32600;
    export const METHOD_NOT_FOUND = -32601;
    export const INVALID_PARAMS = -32602;
    export const INTERNAL_ERROR = -32603;
    
    /** Create a JSON-RPC request. */
    export function createRequest(
      id: string | number,
      method: string,
      params?: Record<string, unknown>,
    ): JsonRpcRequest;
    
    /** Create a JSON-RPC notification. */
    export function createNotification(
      method: string,
      params?: Record<string, unknown>,
    ): JsonRpcNotification;
    
    /** Create a JSON-RPC error response. */
    export function createErrorResponse(
      id: string | number | null,
      error: JsonRpcError,
    ): JsonRpcErrorResponse;
    
    /** Parse a single JSON-RPC message from a JSON value. */
    export function parseMessage(value: unknown): JsonRpcMessage;
    
    /** Transport-agnostic peer for request/response correlation. */
    export interface JsonRpcPeer {
      /** Register a handler for incoming requests/notifications. */
      onRequest(method: string, handler: RequestHandler): void;
    
      /** Send a request and await the correlated response. */
      request<T>(method: string, params?: Record<string, unknown>): Promise<T>;
    
      /** Send a notification (no response expected). */
      notify(method: string, params?: Record<string, unknown>): void;
    
      /** Process an incoming raw message. */
      handleMessage(message: JsonRpcMessage): Promise<JsonRpcMessage | undefined>;
    }
    
    export function createJsonRpcPeer(): JsonRpcPeer;

    McpTransport interface

    import type { JsonRpcMessage } from "./types.ts";
    
    /** Common interface for MCP transports. */
    export interface McpTransport {
      /** Start the transport and begin reading messages. */
      start(): Promise<void>;
    
      /** Send a JSON-RPC message to the peer. */
      send(message: JsonRpcMessage): Promise<void>;
    
      /** Register a callback for incoming messages. */
      onMessage(handler: (message: JsonRpcMessage) => void): void;
    
      /** Close the transport gracefully. */
      close(): Promise<void>;
    }

    stdio_transport.ts

    import type { McpTransport } from "./transport.ts";
    
    export interface StdioTransportOptions {
      /** Readable stream for incoming messages. @default Deno.stdin */
      input?: ReadableStream<Uint8Array>;
      /** Writable stream for outgoing messages. @default Deno.stdout */
      output?: WritableStream<Uint8Array>;
    }
    
    /** Stdio transport using newline-delimited JSON. */
    export class StdioTransport implements McpTransport {
      constructor(options?: StdioTransportOptions);
      start(): Promise<void>;
      send(message: JsonRpcMessage): Promise<void>;
      onMessage(handler: (message: JsonRpcMessage) => void): void;
      close(): Promise<void>;
    }

    http_transport.ts

    import type { McpTransport } from "./transport.ts";
    
    export interface HttpTransportServerOptions {
      port?: number;
      hostname?: string;
      path?: string; // default "/mcp"
    }
    
    export interface HttpTransportClientOptions {
      url: string | URL;
      headers?: Record<string, string>;
    }
    
    /** Streamable HTTP transport (server mode). */
    export class HttpTransport implements McpTransport {
      constructor(options: HttpTransportServerOptions);
      // ...
    }
    
    /** Streamable HTTP transport (client mode). */
    export class HttpClientTransport implements McpTransport {
      constructor(options: HttpTransportClientOptions);
      // ...
    }

    server.ts

    import type { McpTransport } from "./transport.ts";
    import type { CallToolResult, Tool } from "./types.ts";
    
    export interface McpServerOptions {
      name: string;
      version: string;
      instructions?: string;
    }
    
    export interface ToolDefinition {
      name: string;
      description?: string;
      inputSchema: Record<string, unknown>;
      permissions?: Array<"read" | "write" | "net" | "env" | "run" | "ffi">;
      handler: (args: Record<string, unknown>) => Promise<CallToolResult>;
    }
    
    export interface ResourceDefinition {
      uri: string;
      name: string;
      description?: string;
      mimeType?: string;
      handler: (uri: string) => Promise<ResourceContents>;
    }
    
    export interface PromptDefinition {
      name: string;
      description?: string;
      arguments?: PromptArgument[];
      handler: (args: Record<string, string>) => Promise<GetPromptResult>;
    }
    
    /** High-level MCP server. */
    export class McpServer {
      constructor(options: McpServerOptions);
    
      tool(definition: ToolDefinition): this;
      resource(definition: ResourceDefinition): this;
      prompt(definition: PromptDefinition): this;
    
      /** Connect to a transport and start serving. */
      connect(transport: McpTransport): Promise<void>;
    
      /** Gracefully shut down. */
      close(): Promise<void>;
    }

    client.ts

    import type { McpTransport } from "./transport.ts";
    import type { CallToolResult, Prompt, Resource, Tool } from "./types.ts";
    
    export interface McpClientOptions {
      name: string;
      version: string;
    }
    
    /** High-level MCP client. */
    export class McpClient {
      constructor(options: McpClientOptions);
    
      /** Connect to a server transport. */
      connect(transport: McpTransport): Promise<void>;
    
      listTools(): Promise<Tool[]>;
      callTool(
        name: string,
        args?: Record<string, unknown>,
      ): Promise<CallToolResult>;
    
      listResources(): Promise<Resource[]>;
      readResource(uri: string): Promise<ResourceContents>;
    
      listPrompts(): Promise<Prompt[]>;
      getPrompt(
        name: string,
        args?: Record<string, string>,
      ): Promise<GetPromptResult>;
    
      close(): Promise<void>;
    }

    Package layout

    mcp/
    ├── deno.json
    ├── mod.ts
    ├── types.ts
    ├── types_test.ts
    ├── json_rpc.ts
    ├── json_rpc_test.ts
    ├── transport.ts          # McpTransport interface (shared)
    ├── stdio_transport.ts
    ├── stdio_transport_test.ts
    ├── http_transport.ts
    ├── http_transport_test.ts
    ├── server.ts
    ├── server_test.ts
    ├── client.ts
    ├── client_test.ts
    ├── permissions.ts        # optional Deno.permissions helpers
    └── permissions_test.ts
    

    mcp/deno.json

    {
      "name": "@std/mcp",
      "version": "0.1.0",
      "exports": {
        ".": "./mod.ts",
        "./types": "./types.ts",
        "./json-rpc": "./json_rpc.ts",
        "./stdio": "./stdio_transport.ts",
        "./http": "./http_transport.ts",
        "./server": "./server.ts",
        "./client": "./client.ts",
        "./permissions": "./permissions.ts"
      }
    }

    Granular exports mirror json/deno.json and
    msgpack/deno.json, allowing users to import only what
    they need (e.g. @std/mcp/types for types-only usage in browser code).

    mcp/mod.ts

    Follows the pattern from json/mod.ts:

    • High-level module description
    • One runnable example (stdio server with a single tool)
    • @module tag
    • Re-exports from submodules

    Unstable API policy

    Per the contributing guide, all public symbols ship
    with:

    /**
     * <description>
     *
     * > [!WARNING]
     * > **UNSTABLE**: New API, yet to be vetted.
     *
     * @experimental
     */

    The package starts at 0.1.0 (pre-1.0) following std's
    versioning proposal for unstable
    packages.

    Testing

    Each source file has a paired _test.ts. Tests use @std/assert (test-only,
    per contributing guide rule #4865). Test names follow the convention:

    • StdioTransport parses newline-delimited JSON messages
    • McpServer handles initialize and returns server capabilities
    • McpClient callTool() sends tools/call and returns result

    HTTP transport tests can use @std/http test utilities. Stdio transport tests
    can pipe ReadableStream / WritableStream pairs without touching real
    stdin/stdout.

    Spec targeting

    Initial target: 2025-11-25 (stable)

    The first implementation targets the current stable MCP specification:

    Area Details
    Wire format JSON-RPC 2.0
    Lifecycle Stateful initialize / initialized handshake
    Transports stdio (newline-delimited JSON) + Streamable HTTP
    Capabilities Tools, resources, prompts, logging, roots, sampling

    Transport details follow
    Transports (2025-06-18):

    • stdio: one JSON-RPC message per line on stdout; stderr for logging
    • Streamable HTTP: single /mcp endpoint; POST for client-to-server;
      optional SSE for server-to-client streaming; MCP-Protocol-Version header

    Forward compatibility: 2026-07-28 (stateless RC)

    The
    2026-07-28 release candidate
    introduces significant changes:

    Change Impact on @std/mcp
    Removes initialize handshake McpServer / McpClient lifecycle changes behind transport version flag
    Removes Mcp-Session-Id header HttpTransport session management simplified
    Adds required Mcp-Method / Mcp-Name headers HttpTransport header injection
    Protocol version in _meta Types gain _meta fields; version negotiation moves to per-request
    Removes roots, sampling, logging capabilities Types marked deprecated; no SDK support for removed features
    Adds server/discover New client method

    The layered design keeps version-specific behavior behind McpTransport and
    protocol version options, so the type layer remains stable while transports
    adapt:

    const transport = new HttpTransport({
      port: 3000,
      protocolVersion: "2025-11-25", // default
    });
    
    // Later:
    const transport = new HttpTransport({
      port: 3000,
      protocolVersion: "2026-07-28",
    });

    Explicitly not supported

    • Deprecated HTTP+SSE transport from 2024-11-05
    • Removed capabilities in 2026-07-28 (roots, sampling, server-side logging)

    Non-goals

    • LLM / sampling integrations — clients should call LLM provider APIs
      directly (per 2026-07-28 direction)
    • Authentication and authorization — transport security is the deployer's
      responsibility; @std/http provides the HTTP primitives
    • Deprecated transports — no HTTP+SSE from 2024-11-05
    • MCP registry / discovery service — out of scope for a primitives package
    • Code generation from JSON Schema — types are hand-maintained to mirror
      schema.ts; automation can be added later

    Repo-wide touchpoints

    Adding a new package requires updates across the monorepo. These should land in
    follow-up PRs after this proposal is accepted.

    Preceding PR (required first)

    Per CONTRIBUTING.md, add mcp(/unstable)? to the
    scopes list in .github/workflows/title.yml
    before the implementation PR. This enables semantic PR titles like
    feat(mcp): add protocol types.

    Implementation PR touchpoints

    File Change
    deno.json Add "@std/mcp": "jsr:@std/mcp@^0.1.0" to imports; add "./mcp" to workspace
    .github/workflows/title.yml Add mcp(/unstable)? to scopes (if not done in preceding PR)
    _tools/check_docs.ts Add "../mcp/mod.ts" to ENTRY_POINTS
    README.md Add @std/mcp row to the packages table
    _tools/check_import_map.ts May need update if it validates workspace membership

    Validation

    All changes must pass:

    deno task ok

    This runs lint, format check, browser compat check, and the full test suite.

    Implementation phases

    Phase 1: Types + JSON-RPC (foundation)

    • types.ts with full MCP type coverage
    • json_rpc.ts with message builders, parser, and JsonRpcPeer
    • Unit tests with no I/O

    Phase 2: Stdio transport

    • stdio_transport.ts composing @std/json streams
    • Integration test with piped streams
    • Example: minimal stdio server script

    Phase 3: Server API

    • server.ts with McpServer, tool/resource/prompt registration
    • Auto-handle initialize, tools/list, tools/call, etc.
    • End-to-end stdio server test

    Phase 4: HTTP transport

    • http_transport.ts for Streamable HTTP (server + client)
    • SSE response handling
    • Integration test with @std/http test server

    Phase 5: Client API

    • client.ts with McpClient
    • Connect over stdio and HTTP
    • End-to-end client/server test

    Phase 6: Permissions (optional)

    • permissions.ts with requirePermissions() helper
    • Declarative permissions field on tool definitions
    • Documentation and examples

    Open questions

    1. Scope within std

    Does the Deno core team consider an application-protocol package in-scope for
    std? Std has historically been conservative — it provides primitives (http,
    json, streams) rather than application frameworks. MCP is arguably closer to
    @std/http (protocol helpers) than to a web framework, but it is still an
    application-layer protocol tied to a specific ecosystem (LLM tooling).

    Alternative: ship as @denoland/mcp outside std, with std packages as
    dependencies. This reduces maintenance burden on the core team but loses the
    "official Deno MCP SDK" positioning.

    2. Permission integration location

    Should permissions.ts ship in core @std/mcp or as a separate export path
    (@std/mcp/permissions)?

    • Sub-path keeps @std/mcp/types and @std/mcp/json-rpc runtime-neutral
      and browser-compatible
    • Core export makes the Deno differentiator more discoverable

    3. Server + client packaging

    Should server and client APIs live in one package (@std/mcp) or split?

    • One package: simpler discovery, shared types, matches @std/http
      (server + client in one module)
    • Split (@std/mcp-server, @std/mcp-client): smaller dependency footprint
      for server-only users

    Recommendation: one package with granular exports (as proposed), since most
    users will only import @std/mcp/server or @std/mcp/client.

    4. Type maintenance strategy

    Hand-maintained types vs. generated from the official schema.ts?

    • Hand-maintained: easier to review in PRs, matches std conventions, but
      requires manual sync on spec updates
    • Generated: always in sync, but adds a code-generation step to CI

    Recommendation: start hand-maintained; add generation tooling if spec churn
    becomes burdensome.

    5. Browser compatibility

    types.ts and json_rpc.ts can be browser-compatible (no Deno APIs).
    Transports and server/client require Deno. Should browser compat be tested via
    deno task test:browser?

    Recommendation: mark types.ts and json_rpc.ts as browser compatible;
    transports and high-level APIs are Deno-only.

    Alternatives considered

    Use the official TypeScript SDK via npm

    Deno supports npm packages, but:

    • Adds npm dependency management overhead
    • No Deno permission integration
    • Heavier dependency tree than composing std packages
    • Not idiomatic for single-file Deno scripts

    Ship only types (no server/client)

    A types-only package is useful but misses the "zero config single-file server"
    value proposition. The layered design allows types-only usage via
    @std/mcp/types regardless.

    Ship only server (no client)

    Many MCP use cases are server-only (exposing tools to an LLM host). However,
    client support enables testing, scripting, and MCP-to-MCP proxy scenarios with
    minimal extra code since client and server share transports and JSON-RPC core.

    References

  8. fy2ne commented on Jul 3, 2026

    @fy2ne
    Author

    Really well thought out RFC, the layered architecture is clean and the phase order makes sense. Happy to take the client side as you suggested.
    A few things worth talking through before we start building:
    2026-07-28 compat - The protocolVersion flag on HttpTransport handles the header changes fine, but the RC removes the initialize handshake entirely which means capability negotiation shifts out of McpServer lifecycle into per-request _meta. That bleeds into server and client dispatch, not just transport. Worth agreeing on the shape now before Phase 3 locks things in. Single McpServer branching internally on version, or versioned factories?
    Streaming tool results not mentioned but the 2025-11-25 spec allows progressive streaming over SSE on HTTP. Intentional defer or just out of scope for now? Good to have a clear line on it early.
    Std scope honestly the biggest unknown before we invest too much. Worth getting a core team member to drop a signal in this thread so we're not building toward a "not in std" wall.
    On the workflow side, I'm thinking we just keep separate forks and each open our own PR to deno_std when ready, cleaner for the maintainers to review that way. No need to share access to anything. We can co-author commits when we overlap on something though, what email do you use for commits so I have it ready?
    Starting the McpClient skeleton on my end now so we have something concrete to look at.

  9. fy2ne commented on Jul 3, 2026

    @fy2ne
    Author

    On the open questions, I lean toward one package with granular exports as you proposed, and I'd hold off on the @denoland/mcp alternative until we get a signal from the core team. No strong opinion on permissions sub-path vs core, whatever the maintainers prefer.

  10. fy2ne commented on Jul 3, 2026

    @fy2ne
    Author

    Hey @bartlomieju
    before we get into implementation, would love a quick read on whether MCP belongs in std or as a separate @denoland/mcp package. Happy to move forward either way.

  11. luk3skyw4lker commented on Jul 3, 2026

    @luk3skyw4lker
    Contributor

    Yeah, good questions back on the plan

    On the compat side, I lean towards a versioned factory, that way a consumer has to be explicit on which version he wants to use and we avoid confusions

    The streaming tool results were a intentional defer on my end, hadn't dug deep enough to include it in the plan for now, happy to leave it to you to plan and do a follow-up PR as an additional feature for the package after we're done

    I do think the std scope would be very nice though, because the prtoocol is actually becoming as core and standardized as HTTP and other ones, but I agree that maintainers should definitely get a say in this.

    Yep, agreed on the workflow, we might just have to coordinate a little bit with the boostrapping and stuff (since you started the skeleton I can start when that's merged in). My commit email for co-authoring is lucashenriqueblemos@gmail.com

  12. fy2ne commented on Jul 3, 2026

    @fy2ne
    Author

    Versioned factories makes sense, cleaner API, I'll update the client skeleton to reflect that.

    I'll plan out the streaming follow-up PR once the main package lands, happy to own it
    On bootstrapping : i have the client skeleton ready locally but I want to wait for @bartlomieju to weigh in on the std scope question before I push anything.

    Once we get that signal I'll open the skeleton PR first so you can align your foundation files against it. Shouldn't be long.

  13. tomas-zijdemans commented on Sep 26, 2026

    @tomas-zijdemans
    Contributor

    I create a lot of MCP servers, so I'm very interested in what would make sense here.

    However, I’m concerned about drift here. The official TypeScript SDK supports Deno and covers much of this proposal, including transports and client/server APIs. A second implementation would have to track protocol changes (that tend to be big...). What concrete Deno gap would @std/mcp solve that we couldn’t address upstream?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions