Skip to content

Commit 652694d

Browse files
QuantGeekDevclaude
andcommitted
feat: add elicitation support and upgrade SDK to 1.29.0
- Upgrade @modelcontextprotocol/sdk from ^1.11.0 to ^1.29.0 - Add elicit() method for form-based user input (MCP spec 2025-06-18) - Add elicitUrl() method for URL-based out-of-band interaction (MCP spec 2025-11-25) - Add ElicitationFieldSchema type for defining form fields - Re-export ElicitResult, ElicitRequestFormParams, ElicitRequestURLParams from SDK - Fix SDK 1.29.0 type compatibility (inputSchema, outputSchema, content types) - Fix existing test expectations for error response format - 21 unit tests + 11 e2e tests covering form mode, URL mode, all three response actions, schema construction, and integration within execute() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9ddd03f commit 652694d

14 files changed

Lines changed: 1850 additions & 260 deletions

File tree

package-lock.json

Lines changed: 438 additions & 230 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"url": "https://github.com/QuantGeekDev/mcp-framework.git"
5252
},
5353
"peerDependencies": {
54-
"@modelcontextprotocol/sdk": "^1.11.0",
54+
"@modelcontextprotocol/sdk": "^1.29.0",
5555
"zod": "3.x"
5656
},
5757
"dependencies": {
@@ -66,10 +66,11 @@
6666
"prompts": "^2.4.2",
6767
"raw-body": "^2.5.2",
6868
"typescript": "^5.3.3",
69-
"zod": "^3.23.8"
69+
"zod": "3.x"
7070
},
7171
"devDependencies": {
7272
"@eslint/js": "^9.23.0",
73+
"@modelcontextprotocol/sdk": "^1.29.0",
7374
"@types/content-type": "^1.1.8",
7475
"@types/jest": "^29.5.12",
7576
"@types/jsonwebtoken": "^9.0.8",

src/core/MCPServer.ts

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
SubscribeRequestSchema,
1111
UnsubscribeRequestSchema,
1212
CompleteRequestSchema,
13+
SetLevelRequestSchema,
14+
CancelledNotificationSchema,
1315
} from '@modelcontextprotocol/sdk/types.js';
1416
import { ToolProtocol } from '../tools/BaseTool.js';
1517
import { PromptProtocol } from '../prompts/BasePrompt.js';
@@ -31,6 +33,14 @@ import { AuthConfig } from '../auth/types.js';
3133
import { createRequire } from 'module';
3234

3335
const require = createRequire(import.meta.url);
36+
37+
export type MCPLogLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency';
38+
39+
export const LOG_LEVEL_SEVERITY: Record<MCPLogLevel, number> = {
40+
debug: 0, info: 1, notice: 2, warning: 3,
41+
error: 4, critical: 5, alert: 6, emergency: 7,
42+
};
43+
3444
export type TransportType = 'stdio' | 'sse' | 'http-stream';
3545

3646
export interface TransportConfig {
@@ -44,6 +54,7 @@ export interface MCPServerConfig {
4454
version?: string;
4555
basePath?: string;
4656
transport?: TransportConfig;
57+
logging?: boolean;
4758
}
4859

4960
export type ServerCapabilities = {
@@ -58,6 +69,7 @@ export type ServerCapabilities = {
5869
subscribe?: true;
5970
};
6071
completions?: {};
72+
logging?: {};
6173
};
6274

6375
export class MCPServer {
@@ -77,12 +89,16 @@ export class MCPServer {
7789
private transport?: BaseTransport;
7890
private shutdownPromise?: Promise<void>;
7991
private shutdownResolve?: () => void;
92+
private _logLevel: MCPLogLevel = 'warning';
93+
private _loggingEnabled: boolean = false;
94+
private _inFlightAbortControllers = new Map<string | number, AbortController>();
8095

8196
constructor(config: MCPServerConfig = {}) {
8297
this.basePath = this.resolveBasePath(config.basePath);
8398
this.serverName = config.name ?? this.getDefaultName();
8499
this.serverVersion = config.version ?? this.getDefaultVersion();
85100
this.transportConfig = config.transport ?? { type: 'stdio' };
101+
this._loggingEnabled = config.logging ?? false;
86102

87103
if (this.transportConfig.auth && this.transportConfig.options) {
88104
(this.transportConfig.options as any).auth = this.transportConfig.auth;
@@ -253,7 +269,7 @@ export class MCPServer {
253269
return response;
254270
});
255271

256-
targetServer.setRequestHandler(CallToolRequestSchema, async (request: any) => {
272+
targetServer.setRequestHandler(CallToolRequestSchema, async (request: any, extra: any) => {
257273
logger.debug(`Tool call request received for: ${request.params.name}`);
258274
logger.debug(`Tool call arguments: ${JSON.stringify(request.params.arguments)}`);
259275

@@ -272,9 +288,20 @@ export class MCPServer {
272288
method: 'tools/call' as const,
273289
};
274290

275-
const result = await tool.toolCall(toolRequest);
276-
logger.debug(`Tool execution successful: ${JSON.stringify(result)}`);
277-
return result;
291+
// Set progress token and abort signal from the SDK extra context
292+
const progressToken = request.params?._meta?.progressToken;
293+
const abortSignal = extra?.signal as AbortSignal | undefined;
294+
tool.setProgressToken(progressToken);
295+
tool.setAbortSignal(abortSignal);
296+
297+
try {
298+
const result = await tool.toolCall(toolRequest);
299+
logger.debug(`Tool execution successful: ${JSON.stringify(result)}`);
300+
return result;
301+
} finally {
302+
tool.setProgressToken(undefined);
303+
tool.setAbortSignal(undefined);
304+
}
278305
} catch (error) {
279306
const errorMsg = `Tool execution failed: ${error}`;
280307
logger.error(errorMsg);
@@ -399,6 +426,30 @@ export class MCPServer {
399426
return { completion: { values: [] } };
400427
});
401428
}
429+
430+
if (this.capabilities.logging) {
431+
targetServer.setRequestHandler(SetLevelRequestSchema, async (request: any) => {
432+
const level = request.params.level as MCPLogLevel;
433+
if (!LOG_LEVEL_SEVERITY.hasOwnProperty(level)) {
434+
throw new Error(`Invalid log level: ${level}`);
435+
}
436+
this._logLevel = level;
437+
logger.info(`MCP log level set to: ${level}`);
438+
return {};
439+
});
440+
}
441+
442+
targetServer.setNotificationHandler(CancelledNotificationSchema, async (notification: any) => {
443+
const requestId = notification.params.requestId;
444+
if (requestId != null) {
445+
const controller = this._inFlightAbortControllers.get(requestId);
446+
if (controller) {
447+
controller.abort(notification.params.reason ?? 'Request cancelled');
448+
this._inFlightAbortControllers.delete(requestId);
449+
logger.info(`Request ${requestId} cancelled: ${notification.params.reason ?? 'no reason'}`);
450+
}
451+
}
452+
});
402453
}
403454

404455
private async detectCapabilities(): Promise<ServerCapabilities> {
@@ -417,6 +468,11 @@ export class MCPServer {
417468
logger.debug('Resources capability enabled');
418469
}
419470

471+
if (this._loggingEnabled) {
472+
this.capabilities.logging = {};
473+
logger.debug('Logging capability enabled');
474+
}
475+
420476
if (this.capabilities.prompts || this.capabilities.resources) {
421477
this.capabilities.completions = {};
422478
logger.debug('Completions capability enabled');
@@ -608,4 +664,38 @@ export class MCPServer {
608664
get IsRunning(): boolean {
609665
return this.isRunning;
610666
}
667+
668+
/**
669+
* Query the client for its filesystem root boundaries.
670+
* Returns an empty array if the client doesn't support roots.
671+
*/
672+
public async listRoots(): Promise<Array<{ uri: string; name?: string }>> {
673+
if (!this.server) return [];
674+
try {
675+
const result = await this.server.listRoots();
676+
return result.roots ?? [];
677+
} catch {
678+
return [];
679+
}
680+
}
681+
682+
/**
683+
* Send a logging message to the client via the MCP logging protocol.
684+
* Messages below the current log level threshold will be silently dropped.
685+
*/
686+
public async sendLog(level: MCPLogLevel, loggerName: string, data: unknown): Promise<void> {
687+
if (!this._loggingEnabled || !this.server) return;
688+
if (LOG_LEVEL_SEVERITY[level] < LOG_LEVEL_SEVERITY[this._logLevel]) return;
689+
690+
try {
691+
await this.server.sendLoggingMessage({
692+
level,
693+
logger: loggerName,
694+
data,
695+
});
696+
} catch (error) {
697+
// Don't throw on logging failures
698+
logger.debug(`Failed to send log message: ${error}`);
699+
}
700+
}
611701
}

src/prompts/BasePrompt.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from "zod";
2+
import { MCPIcon } from '../tools/BaseTool.js';
23

34
export type CompletionResult = {
45
values: string[];
@@ -29,6 +30,8 @@ export interface PromptProtocol {
2930
description: string;
3031
required?: boolean;
3132
}>;
33+
title?: string;
34+
icons?: MCPIcon[];
3235
};
3336
getMessages(args?: Record<string, unknown>): Promise<
3437
Array<{
@@ -57,6 +60,8 @@ export abstract class MCPPrompt<TArgs extends Record<string, any> = {}>
5760
abstract name: string;
5861
abstract description: string;
5962
protected abstract schema: PromptArgumentSchema<TArgs>;
63+
protected title?: string;
64+
protected icons?: MCPIcon[];
6065

6166
get promptDefinition() {
6267
return {
@@ -67,6 +72,8 @@ export abstract class MCPPrompt<TArgs extends Record<string, any> = {}>
6772
description: schema.description,
6873
required: schema.required ?? false,
6974
})),
75+
...(this.title && { title: this.title }),
76+
...(this.icons && this.icons.length > 0 && { icons: this.icons }),
7077
};
7178
}
7279

src/resources/BaseResource.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CompletionResult } from '../prompts/BasePrompt.js';
2+
import { MCPIcon, ContentAnnotations } from '../tools/BaseTool.js';
23

34
export type ResourceContent = {
45
uri: string;
@@ -12,13 +13,19 @@ export type ResourceDefinition = {
1213
name: string;
1314
description?: string;
1415
mimeType?: string;
16+
title?: string;
17+
icons?: MCPIcon[];
18+
size?: number;
19+
annotations?: ContentAnnotations;
1520
};
1621

1722
export type ResourceTemplateDefinition = {
1823
uriTemplate: string;
1924
name: string;
2025
description?: string;
2126
mimeType?: string;
27+
title?: string;
28+
icons?: MCPIcon[];
2229
};
2330

2431
export interface ResourceProtocol {
@@ -39,6 +46,10 @@ export abstract class MCPResource implements ResourceProtocol {
3946
abstract name: string;
4047
description?: string;
4148
mimeType?: string;
49+
protected title?: string;
50+
protected icons?: MCPIcon[];
51+
protected size?: number;
52+
protected resourceAnnotations?: ContentAnnotations;
4253

4354
protected template?: {
4455
uriTemplate: string;
@@ -51,6 +62,10 @@ export abstract class MCPResource implements ResourceProtocol {
5162
name: this.name,
5263
description: this.description,
5364
mimeType: this.mimeType,
65+
...(this.title && { title: this.title }),
66+
...(this.icons && this.icons.length > 0 && { icons: this.icons }),
67+
...(this.size !== undefined && { size: this.size }),
68+
...(this.resourceAnnotations && Object.keys(this.resourceAnnotations).length > 0 && { annotations: this.resourceAnnotations }),
5469
};
5570
}
5671

@@ -61,6 +76,8 @@ export abstract class MCPResource implements ResourceProtocol {
6176
name: this.name,
6277
description: this.template.description ?? this.description,
6378
mimeType: this.mimeType,
79+
...(this.title && { title: this.title }),
80+
...(this.icons && this.icons.length > 0 && { icons: this.icons }),
6481
};
6582
}
6683

0 commit comments

Comments
 (0)