Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
107767a
save commit
KKonstantinov Dec 7, 2025
68ff665
context API - backwards compatible introduction
KKonstantinov Dec 7, 2025
f58b491
fixes
KKonstantinov Dec 7, 2025
459bff4
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Dec 7, 2025
a23e2f2
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Dec 7, 2025
4ff84a1
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Dec 8, 2025
e89d9d4
moved properties under objects
KKonstantinov Dec 8, 2025
187a3cd
prettier fix
KKonstantinov Dec 8, 2025
96169b3
move logger methods under loggingNotification
KKonstantinov Dec 8, 2025
a57840c
merge commit
KKonstantinov Dec 9, 2025
161f584
merge commit - v2
KKonstantinov Dec 23, 2025
d5f5047
merge commit - v2
KKonstantinov Dec 23, 2025
109dc52
base context, client context, server context separation
KKonstantinov Jan 21, 2026
deca7fd
rename method to createRequestContext, update docs
KKonstantinov Jan 21, 2026
36be75e
fix server conformance
KKonstantinov Jan 21, 2026
f609478
fix conformance
KKonstantinov Jan 21, 2026
3c4f9d8
move types
KKonstantinov Jan 21, 2026
2c31eb2
merge commit
KKonstantinov Jan 22, 2026
86549fa
rename extra vars to ctx
KKonstantinov Jan 22, 2026
920da7e
prettier fix
KKonstantinov Jan 22, 2026
639d7bd
add changeset
KKonstantinov Jan 22, 2026
ae9c253
merge commit
KKonstantinov Jan 23, 2026
a5704c5
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Jan 23, 2026
f39dc4e
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Jan 26, 2026
cb4c500
switch server and client to work with ServerContextInterface and Clie…
KKonstantinov Jan 27, 2026
f5b27d4
Merge branch 'feature/ctx-in-callbacks' of github.com:KKonstantinov/t…
KKonstantinov Jan 27, 2026
9b0ed8d
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Jan 29, 2026
7767079
Merge branch 'main' of github.com:modelcontextprotocol/typescript-sdk…
KKonstantinov Feb 2, 2026
f765e07
update context interface
KKonstantinov Feb 2, 2026
184dbca
Merge branch 'feature/ctx-in-callbacks' of github.com:KKonstantinov/t…
KKonstantinov Feb 2, 2026
69b1ae6
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Feb 2, 2026
9132b5a
update migration docs
KKonstantinov Feb 2, 2026
08da5e0
merge commit
KKonstantinov Feb 3, 2026
5b05ff6
merge commit
KKonstantinov Feb 3, 2026
ebac5d4
update conformance ctx - sampling fail
KKonstantinov Feb 3, 2026
b79763f
Merge branch 'main' into feature/ctx-in-callbacks
KKonstantinov Feb 3, 2026
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
339 changes: 339 additions & 0 deletions src/server/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
import {
CreateMessageRequest,
CreateMessageResult,
ElicitRequest,
ElicitResult,
ElicitResultSchema,
JSONRPCRequest,
LoggingMessageNotification,
Notification,
Request,
RequestId,
RequestInfo,
RequestMeta,
Result,
ServerNotification,
ServerRequest
} from '../types.js';
import { RequestHandlerExtra, RequestOptions, RequestTaskStore } from '../shared/protocol.js';
import { Server } from './index.js';
import { AuthInfo } from './auth/types.js';
import { AnySchema, SchemaOutput } from './zod-compat.js';

/**
* Interface for sending logging messages to the client via {@link LoggingMessageNotification}.
*/
export interface LoggingMessageNotificationSenderInterface {
/**
* Sends a logging message to the client.
*/
log(params: LoggingMessageNotification['params'], sessionId?: string): Promise<void>;
/**
* Sends a debug log message to the client.
*/
debug(message: string, extraLogData?: Record<string, unknown>, sessionId?: string): Promise<void>;
/**
* Sends an info log message to the client.
*/
info(message: string, extraLogData?: Record<string, unknown>, sessionId?: string): Promise<void>;
/**
* Sends a warning log message to the client.
*/
warning(message: string, extraLogData?: Record<string, unknown>, sessionId?: string): Promise<void>;
/**
* Sends an error log message to the client.
*/
error(message: string, extraLogData?: Record<string, unknown>, sessionId?: string): Promise<void>;
}

export class ServerLogger implements LoggingMessageNotificationSenderInterface {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(private readonly server: Server<any, any, any>) {}

/**
* Sends a logging message.
*/
public async log(params: LoggingMessageNotification['params'], sessionId?: string) {
await this.server.sendLoggingMessage(params, sessionId);
}

/**
* Sends a debug log message.
*/
public async debug(message: string, extraLogData?: Record<string, unknown>, sessionId?: string) {
await this.log(
{
level: 'debug',
data: {
...extraLogData,
message
},
logger: 'server'
},
sessionId
);
}

/**
* Sends an info log message.
*/
public async info(message: string, extraLogData?: Record<string, unknown>, sessionId?: string) {
await this.log(
{
level: 'info',
data: {
...extraLogData,
message
},
logger: 'server'
},
sessionId
);
}

/**
* Sends a warning log message.
*/
public async warning(message: string, extraLogData?: Record<string, unknown>, sessionId?: string) {
await this.log(
{
level: 'warning',
data: {
...extraLogData,
message
},
logger: 'server'
},
sessionId
);
}

/**
* Sends an error log message.
*/
public async error(message: string, extraLogData?: Record<string, unknown>, sessionId?: string) {
await this.log(
{
level: 'error',
data: {
...extraLogData,
message
},
logger: 'server'
},
sessionId
);
}
}

export interface ContextInterface<RequestT extends Request = Request, NotificationT extends Notification = Notification>
extends RequestHandlerExtra<ServerRequest | RequestT, NotificationT | ServerNotification> {
elicitInput(params: ElicitRequest['params'], options?: RequestOptions): Promise<ElicitResult>;
requestSampling: (params: CreateMessageRequest['params'], options?: RequestOptions) => Promise<CreateMessageResult>;
loggingNotification: LoggingMessageNotificationSenderInterface;
}
/**
* A context object that is passed to request handlers.
*
* Implements the RequestHandlerExtra interface for backwards compatibility.
*/
export class Context<RequestT extends Request = Request, NotificationT extends Notification = Notification, ResultT extends Result = Result>
implements ContextInterface<RequestT, NotificationT>
{
private readonly server: Server<RequestT, NotificationT, ResultT>;

/**
* The request context.
* A type-safe context that is passed to request handlers.
*/
private readonly requestCtx: RequestHandlerExtra<ServerRequest | RequestT, ServerNotification | NotificationT>;

/**
* The MCP context - Contains information about the current MCP request and session.
*/
public readonly mcpContext: {
/**
* The JSON-RPC ID of the request being handled.
* This can be useful for tracking or logging purposes.
*/
requestId: RequestId;
/**
* The method of the request.
*/
method: string;
/**
* The metadata of the request.
*/
_meta?: RequestMeta;
/**
* The session ID of the request.
*/
sessionId?: string;
};

public readonly task:
| {
id: string | undefined;
store: RequestTaskStore | undefined;
requestedTtl: number | null | undefined;
}
| undefined;

public readonly stream:
| {
/**
* Closes the SSE stream for this request, triggering client reconnection.
* Only available when using StreamableHTTPServerTransport with eventStore configured.
* Use this to implement polling behavior during long-running operations.
*/
closeSSEStream: (() => void) | undefined;
/**
* Closes the standalone GET SSE stream, triggering client reconnection.
* Only available when using StreamableHTTPServerTransport with eventStore configured.
* Use this to implement polling behavior for server-initiated notifications.
*/
closeStandaloneSSEStream: (() => void) | undefined;
}
| undefined;

public readonly loggingNotification: LoggingMessageNotificationSenderInterface;

constructor(args: {
server: Server<RequestT, NotificationT, ResultT>;
request: JSONRPCRequest;
requestCtx: RequestHandlerExtra<ServerRequest | RequestT, ServerNotification | NotificationT>;
}) {
this.server = args.server;
this.requestCtx = args.requestCtx;
this.mcpContext = {
requestId: args.requestCtx.requestId,
method: args.request.method,
_meta: args.requestCtx._meta,
sessionId: args.requestCtx.sessionId
};

this.task = {
id: args.requestCtx.taskId,
store: args.requestCtx.taskStore,
requestedTtl: args.requestCtx.taskRequestedTtl
};

this.loggingNotification = new ServerLogger(args.server);

this.stream = {
closeSSEStream: args.requestCtx.closeSSEStream,
closeStandaloneSSEStream: args.requestCtx.closeStandaloneSSEStream
};
}

/**
* The JSON-RPC ID of the request being handled.
* This can be useful for tracking or logging purposes.
*
* @deprecated Use {@link mcpContext.requestId} instead.
*/
public get requestId(): RequestId {
return this.requestCtx.requestId;
}

public get signal(): AbortSignal {
return this.requestCtx.signal;
}

public get authInfo(): AuthInfo | undefined {
return this.requestCtx.authInfo;
}

public get requestInfo(): RequestInfo | undefined {
return this.requestCtx.requestInfo;
}

/**
* @deprecated Use {@link mcpContext._meta} instead.
*/
public get _meta(): RequestMeta | undefined {
return this.requestCtx._meta;
}

/**
* @deprecated Use {@link mcpContext.sessionId} instead.
*/
public get sessionId(): string | undefined {
return this.mcpContext.sessionId;
}

/**
* @deprecated Use {@link task.id} instead.
*/
public get taskId(): string | undefined {
return this.requestCtx.taskId;
}

/**
* @deprecated Use {@link task.store} instead.
*/
public get taskStore(): RequestTaskStore | undefined {
return this.requestCtx.taskStore;
}

/**
* @deprecated Use {@link task.requestedTtl} instead.
*/
public get taskRequestedTtl(): number | undefined {
return this.requestCtx.taskRequestedTtl ?? undefined;
}

/**
* @deprecated Use {@link stream.closeSSEStream} instead.
*/
public get closeSSEStream(): (() => void) | undefined {
return this.requestCtx.closeSSEStream;
}

/**
* @deprecated Use {@link stream.closeStandaloneSSEStream} instead.
*/
public get closeStandaloneSSEStream(): (() => void) | undefined {
return this.requestCtx.closeStandaloneSSEStream;
}

/**
* Sends a notification that relates to the current request being handled.
*
* This is used by certain transports to correctly associate related messages.
*/
public sendNotification = (notification: NotificationT | ServerNotification): Promise<void> => {
return this.requestCtx.sendNotification(notification);
};

/**
* Sends a request that relates to the current request being handled.
*
* This is used by certain transports to correctly associate related messages.
*/
public sendRequest = <U extends AnySchema>(
request: RequestT | ServerRequest,
resultSchema: U,
options?: RequestOptions
): Promise<SchemaOutput<U>> => {
return this.requestCtx.sendRequest(request, resultSchema, { ...options, relatedRequestId: this.requestId });
};

/**
* Sends a request to sample an LLM via the client.
*/
public requestSampling(params: CreateMessageRequest['params'], options?: RequestOptions) {
return this.server.createMessage(params, options);
}

/**
* Sends an elicitation request to the client.
*/
public async elicitInput(params: ElicitRequest['params'], options?: RequestOptions): Promise<ElicitResult> {
const request: ElicitRequest = {
method: 'elicitation/create',
params
};
return await this.server.request(request, ElicitResultSchema, { ...options, relatedRequestId: this.requestId });
}
}
Loading
Loading