Skip to content

Commit 9f795ad

Browse files
committed
fix(server): reject requests before initialization
1 parent 5fc42e9 commit 9f795ad

3 files changed

Lines changed: 95 additions & 4 deletions

File tree

.changeset/green-dryers-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@modelcontextprotocol/server": patch
3+
---
4+
5+
fix(server): reject requests before initialization

packages/server/src/server/server.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export class Server extends Protocol<ServerContext> {
102102
private _instructions?: string;
103103
private _jsonSchemaValidator: jsonSchemaValidator;
104104
private _experimental?: { tasks: ExperimentalServerTasks };
105+
private _receivedInitialize = false;
106+
private _initialized = false;
105107

106108
/**
107109
* Callback for when initialization has fully completed (i.e., the client has sent an `notifications/initialized` notification).
@@ -132,7 +134,13 @@ export class Server extends Protocol<ServerContext> {
132134
}
133135

134136
this.setRequestHandler('initialize', request => this._oninitialize(request));
135-
this.setNotificationHandler('notifications/initialized', () => this.oninitialized?.());
137+
this.setNotificationHandler('notifications/initialized', () => {
138+
if (!this._receivedInitialize) {
139+
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, 'Server not initialized');
140+
}
141+
this._initialized = true;
142+
this.oninitialized?.();
143+
});
136144

137145
if (this._capabilities.logging) {
138146
this._registerLoggingHandler();
@@ -226,8 +234,19 @@ export class Server extends Protocol<ServerContext> {
226234
method: string,
227235
handler: (request: JSONRPCRequest, ctx: ServerContext) => Promise<Result>
228236
): (request: JSONRPCRequest, ctx: ServerContext) => Promise<Result> {
237+
const lifecycleHandler: (request: JSONRPCRequest, ctx: ServerContext) => Promise<Result> = async (request, ctx) => {
238+
if (!ctx.http && ctx.sessionId === undefined && !this._receivedInitialize && method !== 'initialize' && method !== 'ping') {
239+
throw new ProtocolError(ProtocolErrorCode.InvalidRequest, 'Server not initialized');
240+
}
241+
const result = await handler(request, ctx);
242+
if (method === 'initialize') {
243+
this._receivedInitialize = true;
244+
}
245+
return result;
246+
};
247+
229248
if (method !== 'tools/call') {
230-
return handler;
249+
return lifecycleHandler;
231250
}
232251
return async (request, ctx) => {
233252
const validatedRequest = parseSchema(CallToolRequestSchema, request);
@@ -239,7 +258,7 @@ export class Server extends Protocol<ServerContext> {
239258

240259
const { params } = validatedRequest.data;
241260

242-
const result = await handler(request, ctx);
261+
const result = await lifecycleHandler(request, ctx);
243262

244263
// When task creation is requested, validate and return CreateTaskResult
245264
if (params.task) {

packages/server/test/server/server.test.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { JSONRPCMessage } from '@modelcontextprotocol/core';
2-
import { InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core';
2+
import { InMemoryTransport, LATEST_PROTOCOL_VERSION, ProtocolErrorCode } from '@modelcontextprotocol/core';
33
import { Server } from '../../src/server/server.js';
44

55
describe('Server', () => {
@@ -38,5 +38,72 @@ describe('Server', () => {
3838

3939
await server.close();
4040
});
41+
42+
it('rejects requests before initialize', async () => {
43+
const server = new Server({ name: 'test', version: '1.0.0' }, { capabilities: { tools: {} } });
44+
45+
server.setRequestHandler('tools/list', async () => ({ tools: [] }));
46+
47+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
48+
await server.connect(serverTransport);
49+
50+
const responses: JSONRPCMessage[] = [];
51+
clientTransport.onmessage = message => responses.push(message);
52+
await clientTransport.start();
53+
54+
await clientTransport.send({
55+
jsonrpc: '2.0',
56+
method: 'notifications/initialized'
57+
} as JSONRPCMessage);
58+
59+
await clientTransport.send({
60+
jsonrpc: '2.0',
61+
id: 1,
62+
method: 'tools/list',
63+
params: {}
64+
} as JSONRPCMessage);
65+
66+
await vi.waitFor(() => expect(responses.some(message => 'id' in message && message.id === 1)).toBe(true));
67+
68+
const rejected = responses.find(message => 'id' in message && message.id === 1);
69+
expect(rejected).toMatchObject({
70+
error: {
71+
code: ProtocolErrorCode.InvalidRequest,
72+
message: 'Server not initialized'
73+
}
74+
});
75+
76+
await clientTransport.send({
77+
jsonrpc: '2.0',
78+
id: 2,
79+
method: 'initialize',
80+
params: {
81+
protocolVersion: LATEST_PROTOCOL_VERSION,
82+
capabilities: {},
83+
clientInfo: { name: 'test-client', version: '1.0.0' }
84+
}
85+
} as JSONRPCMessage);
86+
await vi.waitFor(() => expect(responses.some(message => 'id' in message && message.id === 2)).toBe(true));
87+
88+
await clientTransport.send({
89+
jsonrpc: '2.0',
90+
method: 'notifications/initialized'
91+
} as JSONRPCMessage);
92+
93+
await clientTransport.send({
94+
jsonrpc: '2.0',
95+
id: 3,
96+
method: 'tools/list',
97+
params: {}
98+
} as JSONRPCMessage);
99+
100+
await vi.waitFor(() => expect(responses.some(message => 'id' in message && message.id === 3)).toBe(true));
101+
102+
expect(responses.find(message => 'id' in message && message.id === 3)).toMatchObject({
103+
result: { tools: [] }
104+
});
105+
106+
await server.close();
107+
});
41108
});
42109
});

0 commit comments

Comments
 (0)