fix: create fresh Server per request in auth test helpers (#141)

The auth test server helper and no-dns-rebinding-protection example
reused a single Server instance across requests, calling connect()
on each new transport. SDK v1.26.0's security fix (GHSA-345p-7cg4-v4c7)
now throws "Already connected to a transport" in this case.

Fix by wrapping Server creation in a factory function called per
request, matching the pattern already used by tools_call.ts and
everything-server.ts.

Also updates no-dns-rebinding-protection.ts to use registerTool()
instead of the deprecated tool() API.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Felix Weinberger 2026-02-10 19:22:16 +00:00 • committed by GitHub
commit 37225cef7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 47 deletions

View file

@ -13,21 +13,27 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
// Create minimal MCP server
const server = new McpServer({
name: 'no-dns-rebinding-protection-server',
version: '1.0.0'
});
// Create a fresh MCP server per request to avoid "Already connected" errors
// after the v1.26.0 security fix (GHSA-345p-7cg4-v4c7)
function createMcpServer() {
const server = new McpServer({
name: 'no-dns-rebinding-protection-server',
version: '1.0.0'
});
// Add a simple tool
server.tool(
'echo',
'Echo the input back',
{ message: { type: 'string' } },
async ({ message }) => ({
content: [{ type: 'text', text: `Echo: ${message}` }]
})
);
server.registerTool(
'echo',
{
description: 'Echo the input back',
inputSchema: { message: { type: 'string' } }
},
async ({ message }) => ({
content: [{ type: 'text', text: `Echo: ${message}` }]
})
);
return server;
}
// === VULNERABLE EXPRESS APP ===
// This intentionally does NOT use createMcpExpressApp() or localhostHostValidation()
@ -37,6 +43,7 @@ app.use(express.json());
app.post('/mcp', async (req, res) => {
try {
const server = createMcpServer();
// Stateless: no session ID
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined

View file

@ -41,43 +41,49 @@ export function createServer(
tokenVerifier,
prmResourceOverride
} = options;
const server = new Server(
{
name: 'auth-prm-pathbased-server',
version: '1.0.0'
},
{
capabilities: {
tools: {}
}
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'test-tool',
inputSchema: { type: 'object' }
// Factory: create a fresh Server per request to avoid "Already connected" errors
// after the v1.26.0 security fix (GHSA-345p-7cg4-v4c7)
function createMcpServer() {
const server = new Server(
{
name: 'auth-prm-pathbased-server',
version: '1.0.0'
},
{
capabilities: {
tools: {}
}
]
};
});
server.setRequestHandler(
CallToolRequestSchema,
async (request): Promise<CallToolResult> => {
if (request.params.name === 'test-tool') {
return {
content: [{ type: 'text', text: 'test' }]
};
}
throw new McpError(
ErrorCode.InvalidParams,
`Tool ${request.params.name} not found`
);
}
);
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'test-tool',
inputSchema: { type: 'object' }
}
]
};
});
server.setRequestHandler(
CallToolRequestSchema,
async (request): Promise<CallToolResult> => {
if (request.params.name === 'test-tool') {
return {
content: [{ type: 'text', text: 'test' }]
};
}
throw new McpError(
ErrorCode.InvalidParams,
`Tool ${request.params.name} not found`
);
}
);
return server;
}
const app = express();
app.use(express.json());
@ -151,6 +157,7 @@ export function createServer(
authMiddleware(req, res, async (err?: any) => {
if (err) return next(err);
const server = createMcpServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined
});