Skip to content

Commit 9ddaac4

Browse files
refactor: simplify context type hierarchy from 14 types to 4 plain types
Remove all context classes (BaseContext, ServerContext, ClientContext, NotificationLogHelper) and intermediate types (McpReqContext, McpReqContextInput, HttpReqContext, NotificationContext, BaseRequestContext, ServerNotificationContext). Replace with 4 plain types: - TaskContext: conditional task grouping - ContextInterface: base context with nested mcpReq, http?, task?, notification - ServerContextInterface: extends via & with server additions - ClientContextInterface: type alias for ContextInterface POJO construction inlined in Server.createRequestContext() and Client.createRequestContext(). No abstract classes or factories needed. Bug fixes: - requestSampling now passes relatedRequestId (was missing) - Logging methods now route through sendLoggingMessage with NotificationOptions for proper relatedRequestId/relatedTask association Net: -348 lines
1 parent b79763f commit 9ddaac4

File tree

9 files changed

+308
-656
lines changed

9 files changed

+308
-656
lines changed

examples/server/src/simpleStreamableHttp.ts

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,10 @@ const getServer = () => {
6363
}
6464
},
6565
async ({ name }, ctx): Promise<CallToolResult> => {
66-
await ctx.notification.log(
67-
{
68-
level: 'debug',
69-
data: `Starting greet for ${name}`
70-
},
71-
ctx.sessionId
72-
);
66+
await ctx.notification.log({
67+
level: 'debug',
68+
data: `Starting greet for ${name}`
69+
});
7370

7471
return {
7572
content: [
@@ -96,36 +93,27 @@ const getServer = () => {
9693
openWorldHint: false
9794
}
9895
},
99-
async ({ name }, ctx): Promise<CallToolResult> => {
96+
async ({ name }, _ctx): Promise<CallToolResult> => {
10097
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
10198

102-
await server.sendLoggingMessage(
103-
{
104-
level: 'debug',
105-
data: `Starting multi-greet for ${name}`
106-
},
107-
ctx.sessionId
108-
);
99+
await server.sendLoggingMessage({
100+
level: 'debug',
101+
data: `Starting multi-greet for ${name}`
102+
});
109103

110104
await sleep(1000); // Wait 1 second before first greeting
111105

112-
await server.sendLoggingMessage(
113-
{
114-
level: 'info',
115-
data: `Sending first greeting to ${name}`
116-
},
117-
ctx.sessionId
118-
);
106+
await server.sendLoggingMessage({
107+
level: 'info',
108+
data: `Sending first greeting to ${name}`
109+
});
119110

120111
await sleep(1000); // Wait another second before second greeting
121112

122-
await server.sendLoggingMessage(
123-
{
124-
level: 'info',
125-
data: `Sending second greeting to ${name}`
126-
},
127-
ctx.sessionId
128-
);
113+
await server.sendLoggingMessage({
114+
level: 'info',
115+
data: `Sending second greeting to ${name}`
116+
});
129117

130118
return {
131119
content: [
@@ -311,13 +299,10 @@ const getServer = () => {
311299
}
312300
},
313301
async ({ name }, ctx): Promise<GetPromptResult> => {
314-
await ctx.notification.log(
315-
{
316-
level: 'debug',
317-
data: `Starting greeting template for ${name}`
318-
},
319-
ctx.sessionId
320-
);
302+
await ctx.notification.log({
303+
level: 'debug',
304+
data: `Starting greeting template for ${name}`
305+
});
321306

322307
return {
323308
messages: [
@@ -406,13 +391,10 @@ const getServer = () => {
406391
mimeType: 'text/plain'
407392
},
408393
async (_, ctx): Promise<ReadResourceResult> => {
409-
await ctx.notification.log(
410-
{
411-
level: 'debug',
412-
data: `Starting example file 1`
413-
},
414-
ctx.sessionId
415-
);
394+
await ctx.notification.log({
395+
level: 'debug',
396+
data: `Starting example file 1`
397+
});
416398

417399
return {
418400
contents: [

packages/client/src/client/client.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type {
2+
AnySchema,
23
CallToolRequest,
34
ClientCapabilities,
45
ClientNotification,
56
ClientRequest,
67
ClientResult,
78
CompatibilityCallToolResultSchema,
89
CompleteRequest,
10+
ContextInterface,
911
GetPromptRequest,
1012
Implementation,
1113
JSONRPCRequest,
@@ -22,13 +24,15 @@ import type {
2224
MessageExtraInfo,
2325
Notification,
2426
NotificationMethod,
27+
NotificationOptions,
2528
ProtocolOptions,
2629
ReadResourceRequest,
2730
Request,
2831
RequestMethod,
2932
RequestOptions,
3033
RequestTypeMap,
3134
Result,
35+
SchemaOutput,
3236
ServerCapabilities,
3337
SubscribeRequest,
3438
TaskContext,
@@ -42,6 +46,7 @@ import {
4246
AjvJsonSchemaValidator,
4347
assertClientRequestTaskCapability,
4448
assertToolsCallTaskCapability,
49+
buildTaskContext,
4550
CallToolResultSchema,
4651
CompleteResultSchema,
4752
CreateMessageRequestSchema,
@@ -70,7 +75,6 @@ import {
7075

7176
import { ExperimentalClientTasks } from '../experimental/tasks/client.js';
7277
import type { ClientContextInterface } from './context.js';
73-
import { ClientContext } from './context.js';
7478

7579
/**
7680
* Elicitation default application helper. Applies defaults to the data based on the schema.
@@ -472,37 +476,57 @@ export class Client<
472476
abortController: AbortController;
473477
capturedTransport: Transport | undefined;
474478
extra?: MessageExtraInfo;
475-
}): ClientContextInterface<ClientRequest | RequestT, ClientNotification | NotificationT> {
479+
}): ContextInterface<ClientRequest | RequestT, ClientNotification | NotificationT> {
476480
const { request, taskStore, relatedTaskId, taskCreationParams, abortController, capturedTransport, extra } = args;
477481
const sessionId = capturedTransport?.sessionId;
482+
const requestId = request.id;
478483

479-
// Build the task context using the helper from Protocol
480-
const task: TaskContext | undefined = this.buildTaskContext({
484+
const task: TaskContext | undefined = buildTaskContext({
481485
taskStore,
482486
request,
483487
sessionId,
484488
relatedTaskId,
485489
taskCreationParams
486490
});
487491

488-
// Return a ClientContext instance
489-
return new ClientContext<RequestT, NotificationT, ResultT>({
490-
client: this,
491-
request,
492+
// Build send closures that inject relatedRequestId and relatedTask
493+
const sendRequest = async <U extends AnySchema>(
494+
req: ClientRequest | RequestT,
495+
resultSchema: U,
496+
options?: RequestOptions
497+
): Promise<SchemaOutput<U>> => {
498+
const requestOptions: RequestOptions = { ...options, relatedRequestId: requestId };
499+
const taskId = task?.id;
500+
if (taskId) {
501+
requestOptions.relatedTask = { taskId };
502+
if (task?.store) {
503+
await task.store.updateTaskStatus(taskId, 'input_required');
504+
}
505+
}
506+
return this.request(req, resultSchema, requestOptions);
507+
};
508+
509+
const sendNotification = async (notification: ClientNotification | NotificationT): Promise<void> => {
510+
const notificationOptions: NotificationOptions = { relatedRequestId: requestId };
511+
if (task && task.id) {
512+
notificationOptions.relatedTask = { taskId: task.id };
513+
}
514+
return this.notification(notification, notificationOptions);
515+
};
516+
517+
return {
492518
sessionId,
493519
mcpReq: {
494-
id: request.id,
520+
id: requestId,
495521
method: request.method,
496522
_meta: request.params?._meta,
497-
signal: abortController.signal
523+
signal: abortController.signal,
524+
send: sendRequest
498525
},
499-
http: extra?.authInfo
500-
? {
501-
authInfo: extra?.authInfo
502-
}
503-
: undefined,
504-
task: task
505-
});
526+
http: extra?.authInfo ? { authInfo: extra.authInfo } : undefined,
527+
task,
528+
notification: { send: sendNotification }
529+
};
506530
}
507531

508532
protected assertCapability(capability: keyof ServerCapabilities, method: string): void {
Lines changed: 6 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,13 @@
1-
import type {
2-
ClientNotification,
3-
ClientRequest,
4-
ClientResult,
5-
ContextInterface,
6-
HttpReqContext,
7-
JSONRPCRequest,
8-
McpReqContextInput,
9-
Notification,
10-
Request,
11-
Result,
12-
TaskContext
13-
} from '@modelcontextprotocol/core';
14-
import { BaseContext } from '@modelcontextprotocol/core';
15-
16-
import type { Client } from './client.js';
1+
import type { ClientNotification, ClientRequest, ContextInterface, Notification, Request } from '@modelcontextprotocol/core';
172

183
/**
19-
* Type alias for client-side request handler context.
20-
* Extends the base ContextInterface with no additional fields.
21-
* The generic parameters match the Client's combined types.
4+
* Client-specific context type for request handlers.
5+
* Used when the client handles requests from the server (e.g., sampling, elicitation).
6+
*
7+
* @typeParam RequestT - The type of requests that can be sent from this context
8+
* @typeParam NotificationT - The type of notifications that can be sent from this context
229
*/
2310
export type ClientContextInterface<
2411
RequestT extends Request = Request,
2512
NotificationT extends Notification = Notification
2613
> = ContextInterface<ClientRequest | RequestT, ClientNotification | NotificationT>;
27-
28-
/**
29-
* A context object that is passed to client-side request handlers.
30-
* Used when the client handles requests from the server (e.g., sampling, elicitation).
31-
*/
32-
export class ClientContext<
33-
RequestT extends Request = Request,
34-
NotificationT extends Notification = Notification,
35-
ResultT extends Result = Result
36-
>
37-
extends BaseContext<ClientRequest | RequestT, ClientNotification | NotificationT, ClientResult | ResultT>
38-
implements ClientContextInterface<RequestT, NotificationT>
39-
{
40-
private readonly client: Client<RequestT, NotificationT, ResultT>;
41-
42-
constructor(args: {
43-
sessionId?: string;
44-
request: JSONRPCRequest;
45-
mcpReq: McpReqContextInput;
46-
http?: HttpReqContext;
47-
task: TaskContext | undefined;
48-
client: Client<RequestT, NotificationT, ResultT>;
49-
}) {
50-
super({
51-
request: args.request,
52-
sessionId: args.sessionId,
53-
mcpReq: args.mcpReq,
54-
http: args.http,
55-
task: args.task
56-
});
57-
this.client = args.client;
58-
}
59-
60-
/**
61-
* Returns the client instance for sending notifications and requests.
62-
*/
63-
protected getProtocol(): Client<RequestT, NotificationT, ResultT> {
64-
return this.client;
65-
}
66-
}

0 commit comments

Comments
 (0)