Skip to content

Conversation

@KKonstantinov
Copy link
Contributor

Note: This PR is a draft and is to be perceived as a discussion point and a potential "pick list" / "wish list" of features for refactor. Features in this PR may or may not come in, or may come in a different shape or form.

V2 Protocol Refactor: Structured Context, Middleware, and Registry Systems

This PR introduces a comprehensive architectural refactor of the MCP TypeScript SDK, establishing cleaner separation of concerns, a structured context API for request handlers, and an Express-style middleware system.

Summary

Architecture Changes (Internal/Contributors)

  • Internal Plugin System: Decomposed Protocol class into focused, hookable components with lifecycle hooks
  • Error Hierarchy: Comprehensive error types distinguishing protocol errors (cross wire) vs SDK errors (local only)
  • Registry System: First-class entities for tools, resources, and prompts with dynamic management
  • Context Layer: Structured context objects flowing through the request lifecycle

API Changes (External/Users)

  • Structured Context API: All callbacks now receive a typed ctx object (breaking change)
  • Middleware System: Express/Koa-style middleware for cross-cutting concerns
  • Builder Pattern: Fluent API for declarative server configuration
  • Error Handler Callbacks: Customize error responses via onError/onProtocolError
  • Type-Safe Event System: Cross-platform event emitter for observability

Motivation and Context

Problem: Scattered Context and Limited Extensibility

The previous SDK architecture had several limitations:

  1. Fragmented Request Context: Tool/resource/prompt handlers received an extra parameter containing request info, but accessing related capabilities (logging, elicitation, sampling) required keeping references to the server instance and manually correlating request IDs.

  2. No Middleware Support: Cross-cutting concerns like logging, authentication, and rate limiting had to be implemented ad-hoc in each handler, leading to code duplication.

  3. Monolithic Protocol Class: The Protocol class (1,667 lines) contained all message handling, timeout management, progress tracking, and task support in one file. It also mixed client-specific code (e.g., getTask(), listTasks(), cancelTask() methods) with server-specific code (e.g., task request handlers registered conditionally), violating separation of concerns—the abstraction leaked by assuming knowledge of who was using it.

  4. No Runtime Registry Access: Registered tools, resources, and prompts could only be accessed by saving the return value of registerTool()/registerResource()/registerPrompt(). There was no way to query all registered items, filter by enabled/disabled status, or perform bulk operations without maintaining external references.

  5. Inconsistent Error Handling: McpError was used for three semantically different purposes:

    • Protocol errors with spec-mandated codes (e.g., -32700 ParseError)
    • Application errors that users should be able to customize
    • Local SDK errors that never cross the wire (e.g., -32001 RequestTimeout, -32000 ConnectionClosed)

    This made it impossible to distinguish "this error code is locked per spec" from "user can customize this code" from "this error should never be sent over the wire."

Solution: Layered Architecture with Structured Context

graph TB
    subgraph "High-Level API"
        McpServer["McpServer"]
        Builder["McpServerBuilder"]
    end
    
    subgraph "Middleware Layer"
        MW["MiddlewareManager"]
        UnivMW["Universal Middleware"]
        TypeMW["Type-Specific Middleware"]
        PerItemMW["Per-Item Middleware"]
    end
    
    subgraph "Registry Layer"
        ToolReg["ToolRegistry"]
        ResReg["ResourceRegistry"]
        PromptReg["PromptRegistry"]
    end
    
    subgraph "Context Layer"
        ServerCtx["ServerContext"]
        BaseCtx["BaseContext"]
        McpCtx["McpContext"]
        ReqCtx["RequestContext"]
        TaskCtx["TaskContext"]
    end
    
    subgraph "Protocol Layer"
        Server["Server"]
        Protocol["Protocol"]
        Plugins["ProtocolPlugins"]
    end
    
    subgraph "Transport Layer"
        Transport["Transport"]
    end
    
    Builder --> McpServer
    McpServer --> MW
    McpServer --> ToolReg
    McpServer --> ResReg
    McpServer --> PromptReg
    McpServer --> Server
    
    MW --> UnivMW
    MW --> TypeMW
    MW --> PerItemMW
    
    Server --> Protocol
    Protocol --> Plugins
    Protocol --> Transport
    
    ServerCtx --> BaseCtx
    BaseCtx --> McpCtx
    BaseCtx --> ReqCtx
    BaseCtx --> TaskCtx
Loading

Architecture Changes (Internal/Contributors)

These changes affect the SDK's internal structure and are most relevant for contributors and those wanting to understand how the SDK works.

1. Internal Plugin System

The Protocol class has been decomposed via an internal plugin system—the most significant architectural change in this PR:

graph LR
    subgraph "Protocol Plugins (Internal)"
        TP[TaskPlugin<br/>Message queueing]
        TCP[TaskClientPlugin<br/>Client-side tasks]
        PM[ProgressManager<br/>Progress tracking]
        TM[TimeoutManager<br/>Request timeouts]
    end
    
    Protocol --> TP
    Protocol --> TCP
    Protocol --> PM
    Protocol --> TM
Loading

Plugins hook into the request/response lifecycle without modifying core Protocol logic:

  • install() - Register handlers during setup
  • onRequest() / onRequestResult() - Intercept request processing
  • shouldRouteMessage() / routeMessage() - Custom message routing (e.g., task queueing)
  • onBuildHandlerContext() - Contribute context fields (e.g., task context)

Why plugins instead of a monolithic Protocol?

  • Separation of concerns: Each plugin owns one responsibility (timeouts, progress, tasks)
  • Testability: Plugins can be tested in isolation
  • Extensibility: New behaviors can be added without modifying core Protocol
  • Maintainability: ~1500-line Protocol.ts is now manageable

Plugin usage example:

// Server-side: Enable task support
server.usePlugin(
    new TaskPlugin({
        taskStore,
        taskMessageQueue: new InMemoryTaskMessageQueue()
    })
);

// Client-side: Enable task polling
client.usePlugin(new TaskClientPlugin({
    defaultPollInterval: 5000
}));

2. Error Hierarchy

A structured error hierarchy distinguishes errors by their semantics:

graph TD
    subgraph "Crosses the Wire (JSON-RPC)"
        PE[ProtocolError<br/>Locked codes: -32700, -32600, etc.]
        UE[User Error<br/>Plain Error thrown in handlers<br/>Customizable via onError callback]
    end
    
    subgraph "Local Errors (SDK-side only)"
        SE[SdkError]
        StateE[StateError<br/>NOT_CONNECTED, ALREADY_CONNECTED]
        CapE[CapabilityError<br/>CAPABILITY_NOT_SUPPORTED]
        TransE[TransportError<br/>CONNECTION_FAILED, TIMEOUT, SEND_FAILED]
        ValE[ValidationError<br/>INVALID_SCHEMA, INVALID_REQUEST]
    end
    
    SE --> StateE
    SE --> CapE
    SE --> TransE
    SE --> ValE
Loading

Error types:

  • ProtocolError: SDK-generated protocol violations with spec-mandated codes (e.g., -32700 ParseError, -32602 InvalidParams). Users can also throw ProtocolError directly to signal a specific locked code.
  • Plain Error: User-thrown errors in handlers become JSON-RPC errors. Customize the response (code, message, data) via the onError callback.
  • SdkError subclasses: Local errors that never cross the wire—they are rejected/thrown locally.

Why this matters internally:

  • Clear distinction between errors that become JSON-RPC responses vs local errors
  • Protocol code can handle each error type appropriately
  • ProtocolError → locked code, onProtocolError can only customize message/data
  • Plain Error → customizable code/message/data via onError

3. Registry System & Class-Based Entities

Tools, resources, and prompts are now managed by ToolRegistry, ResourceRegistry, and PromptRegistry classes. Each registered item is a class instance (RegisteredTool, RegisteredResource, RegisteredPrompt) with private fields.

Before (POJOs with public properties):

// External code could bypass update() and skip notifications
registeredTool.enabled = false;  // No sendToolListChanged() called!
registeredTool.description = 'Hacked';  // Silent mutation

After (classes with private fields):

// All mutations go through methods that trigger notifications
class RegisteredTool {
  #enabled: boolean;  // Private field
  #description: string;
  
  disable(): this {
    this.#enabled = false;
    this.#registry.notifyChanged();  // Always fires
    return this;
  }
}

Runtime registry access:

// Access registries anytime via McpServer (no need to save references)
const allTools = server.tools.values();           // All tools
const enabledTools = server.tools.getEnabled();   // Only enabled
const greetTool = server.tools.get('greet');      // By name

// Bulk operations
server.tools.disableAll();
server.resources.enableAll();

Why registries & class-based entities?

  • Encapsulation: Private fields (#enabled, #name) prevent bypassing notification callbacks
  • Runtime access: Query all registered items without saving individual references
  • Bulk operations: disableAll(), enableAll() for maintenance windows, feature flags
  • Separation of concerns: Registry logic isolated from McpServer

4. Context Layer

Structured context objects flow through the request lifecycle:

// Context composition (internal)
ServerContext = {
  mcpCtx: McpContext,      // MCP-level: requestId, method, sessionId
  requestCtx: RequestContext,  // Request-level: signal, authInfo, transport details
  taskCtx?: TaskContext,   // Task-level: task ID, store (when enabled)
  // + helper methods
}

Why structured context?

  • Each layer is independently typed and testable
  • Plugins can contribute to specific layers via onBuildHandlerContext()
  • Clear ownership: McpContext from Protocol, RequestContext from transport, TaskContext from plugin

5. New Components

Class Purpose Integration Point
TimeoutManager Manages request timeout timers with reset-on-progress support Protocol
ProgressManager Tracks progress notifications and maps tokens to handlers Protocol
HandlerRegistry Type-safe registry for request/notification handlers Protocol
TaskPlugin Server-side task support (queueing, status updates, polling) Protocol via usePlugin()
TaskClientPlugin Client-side task support (polling, result streaming) Protocol via usePlugin()
ToolRegistry Manages registered tools with lifecycle methods McpServer
ResourceRegistry Manages registered resources with lifecycle methods McpServer
PromptRegistry Manages registered prompts with lifecycle methods McpServer
MiddlewareManager Server-side Express-style middleware execution McpServer
ClientMiddlewareManager Client-side middleware execution Client

API Changes (External/Users)

These changes affect the SDK's public API and are most relevant for developers building MCP servers and clients.

1. Structured Context Object (Breaking)

Handlers now receive a typed ctx object with clear namespacing:

// Before: Scattered information, manual correlation
server.tool('my-tool', schema, async (args, extra) => {
  // extra.requestId, extra.signal exist but...
  // How do I log? How do I elicit input? Need server reference!
});

// After: Structured context with helpers
server.tool('my-tool', { inputSchema: schema }, async (args, ctx) => {
  // ctx.mcpCtx - MCP-level info (requestId, method, sessionId)
  // ctx.requestCtx - HTTP details (signal, authInfo, uri, headers)
  // ctx.taskCtx - Task context (if enabled)
  
  // Helper methods bound to this request:
  await ctx.loggingNotification.info('Processing...');
  const input = await ctx.elicitInput({ message: 'Confirm?', ... });
  await ctx.sendNotification({ method: '...', params: {...} });
});

2. Middleware System

Express/Koa-style middleware for cross-cutting concerns:

sequenceDiagram
    participant Client
    participant Universal as Universal MW
    participant TypeMW as Tool MW
    participant PerItem as Per-Tool MW
    participant Handler
    
    Client->>Universal: Request
    Universal->>Universal: Pre-processing
    Universal->>TypeMW: next()
    TypeMW->>TypeMW: Pre-processing
    TypeMW->>PerItem: next()
    PerItem->>PerItem: Auth check
    PerItem->>Handler: next()
    Handler-->>PerItem: Result
    PerItem-->>TypeMW: Result
    TypeMW->>TypeMW: Post-processing
    TypeMW-->>Universal: Result
    Universal->>Universal: Post-processing
    Universal-->>Client: Response
Loading
// Universal middleware (runs for all request types)
server.useMiddleware(async (ctx, next) => {
  const start = Date.now();
  const result = await next();
  console.log(`${ctx.type} completed in ${Date.now() - start}ms`);
  return result;
});

// Type-specific middleware
server.useToolMiddleware(async (ctx, next) => {
  if (!ctx.authInfo?.scopes?.includes('tools')) {
    throw new Error('Unauthorized');
  }
  return next();
});

// Per-registration middleware
// TODO: .tool signature introduced in builder pattern and supports middleware arg, 
// should be added to .registerTool on the McpServer class as well
const server = McpServer.builder()
  .name('my-server')
  .version('1.0.0')
  .tool('admin-action', {
  inputSchema: { ... },
  // Tool-specific middleware. Probably not as useful as tool-wide or universal middleware, as someone could wrap their tool handler with some of this logic anyway via a decorator design pattern or similar.
  middleware: async (ctx, next) => {
    if (!isAdmin(ctx.authInfo)) throw new Error('Admin only');
    return next();
  }
}, handler);

Why middleware?

  • DRY principle: Authentication, logging, rate limiting defined once, applied everywhere
  • Composable: Stack multiple middleware for layered processing
  • Granular control: Universal → type-specific → per-item middleware levels
  • Familiar pattern: Express/Koa developers already know this model

3. Builder Pattern

Fluent API for declarative server configuration:

const server = McpServer.builder()
  .name('my-server')
  .version('1.0.0')
  .useMiddleware(createLoggingMiddleware())
  .useToolMiddleware(authMiddleware)
  .tool('greet', {
    description: 'Greet a user',
    inputSchema: { name: z.string() }
  }, async ({ name }, ctx) => {
    await ctx.loggingNotification.info(`Greeting ${name}`);
    return { content: [text(`Hello, ${name}!`)] };
  })
  .onError((error, ctx) => {
    // Customize error responses
    return { code: 500, message: 'Something went wrong' };
  })
  .build();

Why builder pattern?

  • Declarative configuration: Server setup reads like a specification, not imperative code
  • Method chaining: Fluent API makes configuration concise and readable
  • Pre-validation: Builder can validate configuration before creating the server
  • Immutable setup: Tools, resources, prompts registered during build are ready at construction

4. Registry Access & Bulk Operations

Registries are now accessible at runtime without saving individual references:

// Access all registered items anytime
const allTools = server.tools.values();           // All tools
const enabledTools = server.tools.getEnabled();   // Only enabled
const disabledTools = server.tools.getDisabled(); // Only disabled
const greetTool = server.tools.get('greet');      // By name

// Bulk operations
server.tools.disableAll();
server.resources.enableAll();

// Iterate over all registered items
for (const [name, tool] of server.tools.getAll()) {
  console.log(`Tool: ${name}, enabled: ${tool.enabled}`);
}

Why registry access?

  • Runtime discovery: Query all registered items without maintaining external references
  • Bulk operations: disableAll(), enableAll() for maintenance windows, feature flags
  • Filtering: getEnabled(), getDisabled() for status-based queries
  • Admin endpoints: Users could potentially build introspection APIs that list server capabilities

5. Error Handler Callbacks

Customize error responses sent to clients:

// Handle application errors (tool/resource/prompt handler failures)
const unsubscribe = server.onError(async (error, ctx) => {
  console.error(`Error in ${ctx.type}/${ctx.name}: ${error.message}`);
  
  // Return customized error response
  return {
    code: -32000,  // Custom code allowed
    message: `Application error: ${error.message}`,
    data: { type: ctx.type, requestId: ctx.requestId }
  };
});

// Handle protocol errors (method not found, parse error, invalid params)
server.onProtocolError(async (error, ctx) => {
  console.error(`Protocol error in ${ctx.method}: ${error.message}`);
  
  // Code is LOCKED for protocol errors (spec-mandated)
  // Can only customize message and data
  return { 
    message: `Protocol error: ${error.message}`,
    data: { method: ctx.method }
  };
});

Key differences between handlers:

Aspect onError onProtocolError
Triggered by Tool/resource/prompt handler errors Parse error, method not found, invalid params
Error code Customizable Locked (spec-mandated)
Return type { code?, message?, data? } { message?, data? }
Context.type 'tool' | 'resource' | 'prompt' 'protocol'

ErrorContext provided to handlers:

Server:

interface ErrorContext {
  type: 'tool' | 'resource' | 'prompt' | 'protocol';
  name?: string;      // Tool/resource/prompt name (if applicable)
  method: string;     // JSON-RPC method
  requestId: string;  // JSON-RPC request ID
}

Client:

interface ErrorContext {
    type: 'sampling' | 'elicitation' | 'rootsList' | 'protocol';
    method: string;
    requestId: string;
}

Return value options:

  • undefined / void - Use default error handling
  • string - Use as error message
  • { code?, message?, data? } - Customize error response
  • Error - Wrap and use as error

Why error handler callbacks?

  • Centralized error handling: Define error transformation logic once, apply to all handlers
  • Semantic distinction: Different handling for "protocol violations" vs "application errors"
  • Spec compliance: Protocol error codes are locked per MCP spec; application codes are customizable
  • Observability: Log, track, or transform errors before they reach the client

6. Type-Safe Event System

Cross-platform event emitter for observability:

// Subscribe to connection events
const unsubscribe = server.server.events.on('connection:opened', ({ sessionId }) => {
  console.log(`Client connected: ${sessionId}`);
});

server.server.events.on('connection:closed', ({ sessionId, reason }) => {
  console.log(`Client disconnected: ${sessionId} - ${reason}`);
});

server.server.events.on('error', ({ error, context }) => {
  logger.error(`Error in ${context}:`, error);
});

// Unsubscribe when done
unsubscribe();

Why a custom event system?

  • Cross-platform: Node's EventEmitter doesn't work in browsers or edge runtimes; this implementation works everywhere
  • Type-safe: Event names and payloads are fully typed via generics—no any types or string-based lookups
  • Modern API: Returns unsubscribe function from on() instead of requiring manual off() calls
  • Safe iteration: Listeners can unsubscribe during iteration without breaking other listeners
  • Observability: Hook into connection lifecycle, errors, and protocol events for logging, metrics, or debugging

Available events (Protocol-level):

Event Payload Description
connection:opened { sessionId } Transport connected
connection:closed { sessionId, reason? } Transport disconnected
error { error, context? } Error occurred

Pre-defined event maps for future expansion:

  • McpServerEvents - Tool/resource/prompt registration, connection lifecycle
  • McpClientEvents - Tool calls, results, connection lifecycle

7. Content Helpers

New utility functions for creating tool result content:

import { text, image, audio, resource } from '@modelcontextprotocol/server';

return {
  content: [
    text('Hello, world!'),
    image(base64Data, 'image/png'),
    resource('file:///path', 'text/plain', 'File contents')
  ]
};

Why content helpers?

  • Type-safe: Functions return properly typed ContentBlock objects
  • Concise: Replaces verbose object literals with readable function calls
  • Consistent: Ensures correct structure for text, image, audio, and embedded resources

How Has This Been Tested?

  • Typecheck, lint, conformance tests passing

TODO:

  • Test examples
  • Update unit & integration tests
  • Add additional integration tests

Breaking Changes

1. Tool/Resource/Prompt Callback Signatures

Before:

server.tool('name', schema, async (args, extra) => { ... });

After:

server.tool('name', { inputSchema: schema }, async (args, ctx) => { ... });

The second parameter is now a config object, and the callback receives ctx instead of extra.

2. Context Object Structure

Before:

// extra had flat properties
extra.requestId
extra.signal
extra.authInfo

After:

// ctx has namespaced properties
ctx.mcpCtx.requestId
ctx.requestCtx.signal
ctx.requestCtx.authInfo

3. Server-Initiated Requests from Handlers

Before:

// Required keeping server reference and manual correlation
async (args, extra) => {
  await server.createMessage({ ... }, { relatedRequestId: extra.requestId });
}

After:

// Helper methods are bound to the request context
async (args, ctx) => {
  await ctx.requestSampling({ ... });  // Automatically correlated
  await ctx.elicitInput({ ... });      // Automatically correlated
  await ctx.loggingNotification.info('...');  // Automatically correlated
}

4. Deprecated Method Signatures Removed

The legacy overloaded signatures for .tool(), .resource(), .prompt() that accepted positional arguments have been removed. Use the config object pattern instead.

Migration Guide

// BEFORE (v1)
server.tool('my-tool', 'Description', { arg: z.string() }, async (args, extra) => {
  console.log('Request ID:', extra.requestId);
  return { content: [{ type: 'text', text: 'result' }] };
});

// AFTER (v2)
server.tool('my-tool', {
  description: 'Description',
  inputSchema: { arg: z.string() }
}, async (args, ctx) => {
  console.log('Request ID:', ctx.mcpCtx.requestId);
  await ctx.loggingNotification.debug('Processing request');
  return { content: [text('result')] };
});

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional Context

Architecture Decisions

Why structured context over flat extras?

  • Discoverability: IDE autocomplete shows available properties organized by category
  • Type safety: Each context layer has its own type, enabling precise typing
  • Extensibility: New context fields can be added to specific layers without polluting others
  • Consistency: Same pattern across client and server contexts

Why internal plugins instead of public API?

  • Stability: Internal plugins can change between versions without breaking users
  • Simplicity: Users get middleware for application concerns; plugins are for SDK internals
  • Focus: Plugins handle protocol-level concerns (tasks, progress); middleware handles app concerns (logging, auth)

Why Express-style middleware?

  • Familiarity: Most developers know the (ctx, next) => { ... next() ... } pattern
  • Flexibility: Can abort, short-circuit, modify input, or post-process results
  • Composability: Middleware can be created once and reused across servers

New Exports

// Server package
export { McpServerBuilder, createServerBuilder } from './server/builder.js';
export { ServerContext, ServerContextInterface } from './server/context.js';
export { MiddlewareManager, createLoggingMiddleware, ... } from './server/middleware.js';
export { ToolRegistry, ResourceRegistry, PromptRegistry } from './server/registries/index.js';

// Core package
export { BaseContext, McpContext, TaskContext } from './shared/context.js';
export { ProtocolError, SdkError, StateError, CapabilityError, TransportError, ValidationError, ... } from './errors.js';
export { text, image, audio, resource } from './util/content.js';  // Content helpers
export { TypedEventEmitter, McpEventEmitter } from './shared/events.js';  // Event system
export { McpServerEvents, McpClientEvents } from './shared/events.js';  // Event type maps

// Client package
export { ClientContext, ClientContextInterface } from './client/context.js';

Future Work (not done in this PR)

Split / Abstract away Transport Classes

Problem: WebStandardStreamableHTTPServerTransport (994 lines) handles too many concerns.
Solution: Split into focused / interfaced componsens (e.g. HttpRequestRouter, SessionManager, ResumabilityManager, SecurityMiddleware)

Lifecycle Hooks

Problem: No hooks for server/client lifecycle events that run once (not per-connection).

State Introspection

Problem: Beyond getTool()/getResource()/getPrompt() retrieval, users may want deeper inspection for debugging and admin endpoints.

Debug Mode

Problem: No built-in verbose logging for SDK internals.
Solution: MCP_DEBUG environment variable that enables verbose internal logging for:

Performance Hooks

Problem: No built-in instrumentation points for internal metrics/tracing.
Solution: Internal hooks at key points:

  • Request/response timing
  • Transport latency
  • Handler execution time
  • Queue depths

Fixed: Local Timeout/ConnectionClosed Errors Now Use TransportError

Previous Issue: The SDK previously used McpError/ProtocolError with code -32001 (RequestTimeout) and -32000 (ConnectionClosed) for local errors that never cross the wire. This was semantically inconsistent.

Resolution: These errors now use TransportError (an SdkError subclass):

Error Scenario Previous Type New Type
Request timeout ProtocolError(RequestTimeout) TransportError.requestTimeout()
Connection closed ProtocolError(ConnectionClosed) TransportError.connectionClosed()

Why this is correct:

  1. ProtocolError is semantically defined as "protocol errors that cross the wire as JSON-RPC error responses"
  2. Local timeout/connection errors never cross the wire—they are rejected locally
  3. The remote side receives notifications/cancelled for timeouts, not an error response
  4. TransportError properly represents local transport-layer issues

Status: ✅ Fixed in this PR

@changeset-bot
Copy link

changeset-bot bot commented Jan 26, 2026

🦋 Changeset detected

Latest commit: caa3164

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@modelcontextprotocol/express Patch
@modelcontextprotocol/hono Patch
@modelcontextprotocol/node Patch
@modelcontextprotocol/eslint-config Patch
@modelcontextprotocol/test-integration Patch
@modelcontextprotocol/client Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 27, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@1426

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@1426

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@1426

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@1426

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@1426

commit: caa3164

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant