Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/vscode-inproc-mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 1.0.0 - 21 July 2026
## 1.0.0 - 23 July 2026
### Changed
* The repository has been relocated to [microsoft/vscode-containers](https://github.com/microsoft/vscode-containers). The bump to 1.0.0 does not reflect any breaking API changes.
* Updated to use `@modelcontextprotocol/server` v2.0.0-beta.4.
* Switched in-proc HTTP transport wiring to `@modelcontextprotocol/node`'s `NodeStreamableHTTPServerTransport`.

## 0.3.0 - 9 February 2026
### Breaking Changes
Expand Down
3 changes: 2 additions & 1 deletion packages/vscode-inproc-mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"dependencies": {
"@microsoft/vscode-azext-utils": "catalog:",
"@microsoft/vscode-processutils": "workspace:*",
"@modelcontextprotocol/sdk": "^1.26.0",
"@modelcontextprotocol/node": "2.0.0-beta.4",
"@modelcontextprotocol/server": "2.0.0-beta.4",
Comment thread
bwateratmsft marked this conversation as resolved.
"express": "~5",
"zod": "catalog:"
},
Expand Down
60 changes: 42 additions & 18 deletions packages/vscode-inproc-mcp/src/mcp/registerMcpTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
import type { McpServer, RegisteredTool, ServerContext, StandardSchemaWithJSON } from '@modelcontextprotocol/server';
import type { z } from 'zod/mini';
import type { CopilotTool, ToolIOSchema } from '../contracts/CopilotTool';
import { McpTool } from './McpTool';
Expand Down Expand Up @@ -54,28 +53,53 @@ export function registerMcpTool<TInSchema extends ToolIOSchema, TOutSchema exten
normalizedOutputSchema = mcpTool.outputSchema;
}

const mcpInputSchema = toMcpServerSchema(normalizedInputSchema);
const mcpOutputSchema = toMcpServerSchema(normalizedOutputSchema);

if (mcpInputSchema === undefined) {
return server.registerTool(
mcpTool.name,
{
title: mcpTool.title,
description: mcpTool.description,
outputSchema: mcpOutputSchema,
annotations: mcpTool.annotations,
},
async (extra: ServerContext) => {
return mcpTool.executeMcp.call(mcpTool, undefined as z.infer<TInSchema>, toToolExecutionExtras(extra));
}
);
}

return server.registerTool(
mcpTool.name,
{
...mcpTool,
inputSchema: normalizedInputSchema,
outputSchema: normalizedOutputSchema,
title: mcpTool.title,
description: mcpTool.description,
inputSchema: mcpInputSchema,
outputSchema: mcpOutputSchema,
annotations: mcpTool.annotations,
},
async (input: unknown, extra: RequestHandlerExtra<never, never>) => {
// If the input is void, MCP SDK will call with (extra) instead of (undefined, extra)
// We won't want that, so detect that case and call appropriately
if (inputIsRequestHandlerExtra(input)) {
return mcpTool.executeMcp.call(mcpTool, undefined as z.infer<TInSchema>, input);
} else {
return mcpTool.executeMcp.call(mcpTool, input as z.infer<TInSchema>, extra);
}
async (input: unknown, extra: ServerContext) => {
return mcpTool.executeMcp.call(mcpTool, input as z.infer<TInSchema>, toToolExecutionExtras(extra));
}
);
}

function inputIsRequestHandlerExtra(input: unknown): input is RequestHandlerExtra<never, never> {
return !!input &&
typeof input === 'object' &&
'signal' in input &&
input.signal instanceof AbortSignal;
function toMcpServerSchema(schema: ToolIOSchema | undefined): StandardSchemaWithJSON<unknown, unknown> | undefined {
if (!schema) {
return undefined;
}

// zod/mini schemas implement the Standard Schema contract expected by @modelcontextprotocol/server,
// but the TypeScript package types don't currently line up.
return schema as unknown as StandardSchemaWithJSON<unknown, unknown>;
}

function toToolExecutionExtras(context: ServerContext) {
return {
signal: context.mcpReq.signal,
requestId: context.mcpReq.id,
sessionId: context.sessionId,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { McpServer } from '@modelcontextprotocol/server';
import type * as vscode from 'vscode';

/**
Expand Down
41 changes: 21 additions & 20 deletions packages/vscode-inproc-mcp/src/vscode/inProcHttpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/

import type { DisposableLike } from '@microsoft/vscode-processutils';
import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import * as crypto from 'crypto';
import type * as express from 'express';
import * as fs from 'fs';
Expand All @@ -15,7 +14,12 @@ import { getErrorMessage } from '../utils/getErrorMessage';
import { Lazy } from '../utils/Lazy';
import type { McpProviderOptions } from './McpProviderOptions';

const transports: Record<string, StreamableHTTPServerTransport> = {};
type SessionTransport = {
handleRequest: (req: express.Request, res: express.Response, parsedBody?: unknown) => Promise<void>;
close: () => Promise<void>;
};

const transports: Record<string, SessionTransport> = {};

/**
* Starts a new MCP HTTP server instance on a random named pipe (Windows) or Unix socket (Unix).
Expand Down Expand Up @@ -88,19 +92,16 @@ function authMiddleware(nonce: string, req: express.Request, res: express.Respon

async function handlePost(mcpOptions: McpProviderOptions, req: express.Request, res: express.Response): Promise<void> {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
const { isInitializeRequest, McpServer } = await mcpServerModuleLazy.value;
const { NodeStreamableHTTPServerTransport } = await mcpNodeModuleLazy.value;

const isInitializeRequest = await isInitializeRequestLazy.value;
const { StreamableHTTPServerTransport } = await streamableHttpLazy.value;

let transport: StreamableHTTPServerTransport;
let transport: SessionTransport;
if (sessionId && transports[sessionId]) {
// Existing session
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
// New session initialization request
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore MCP SDK contains a bug where this type mismatches between CJS and ESM, we must ignore it. We also can't do @ts-expect-error because the error only happens when building CJS.
transport = new StreamableHTTPServerTransport({
transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
onsessioninitialized: (sessionId) => {
transports[sessionId] = transport;
Expand All @@ -112,7 +113,6 @@ async function handlePost(mcpOptions: McpProviderOptions, req: express.Request,
allowedHosts: ['localhost'],
});

const { McpServer } = await mcpServerLazy.value;
const server = new McpServer(
{
name: mcpOptions.id,
Expand All @@ -122,9 +122,14 @@ async function handlePost(mcpOptions: McpProviderOptions, req: express.Request,
);

try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore MCP SDK contains a bug where this type mismatches between CJS and ESM, we must ignore it. We also can't do @ts-expect-error because the error only happens when building CJS.
await Promise.resolve(mcpOptions.registerTools(server));
// ESM and CJS declarations expose nominally distinct McpServer classes.
// Both builds share runtime shape, so bridge via the option's parameter type.
await Promise.resolve(
mcpOptions.registerTools(
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
server as unknown as Parameters<McpProviderOptions['registerTools']>[0]
)
);
} catch (err) {
// Failed to register tools, return error
res.status(500).json({
Expand All @@ -138,7 +143,7 @@ async function handlePost(mcpOptions: McpProviderOptions, req: express.Request,
return;
}

await server.connect(transport);
await server.connect(transport as unknown as Parameters<typeof server.connect>[0]);
} else {
// Invalid request
res.status(400).json({
Expand Down Expand Up @@ -206,9 +211,5 @@ function tryCleanupSocket(socketPath: string | undefined): void {

// Lazily load some modules that are only needed when an MCP server is actually started
const expressLazy = new Lazy(async () => await import('express'));
const streamableHttpLazy = new Lazy(async () => await import('@modelcontextprotocol/sdk/server/streamableHttp.js'));
const mcpServerLazy = new Lazy(async () => await import('@modelcontextprotocol/sdk/server/mcp.js'));
const isInitializeRequestLazy = new Lazy(async () => {
const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js');
return isInitializeRequest;
});
const mcpServerModuleLazy = new Lazy(async () => await import('@modelcontextprotocol/server'));
const mcpNodeModuleLazy = new Lazy(async () => await import('@modelcontextprotocol/node'));
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { McpServer } from '@modelcontextprotocol/server';
import type { CopilotTool, ToolIOSchema } from '../contracts/CopilotTool';
import { registerMcpTool } from '../mcp/registerMcpTool';
import { McpToolWithTelemetry } from './McpToolWithTelemetry';
Expand Down
Loading
Loading