@mctx-ai/mcp-server 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +33 -12
- package/dist/security.js +21 -9
- package/dist/server.js +17 -12
- package/package.json +1 -1
- package/src/index.d.ts +33 -12
- package/src/security.js +21 -9
- package/src/server.js +17 -12
package/dist/index.d.ts
CHANGED
|
@@ -187,7 +187,10 @@ export type ToolHandler = {
|
|
|
187
187
|
* ```
|
|
188
188
|
*/
|
|
189
189
|
export type GeneratorToolHandler = {
|
|
190
|
-
(
|
|
190
|
+
(
|
|
191
|
+
args: Record<string, any>,
|
|
192
|
+
ask?: AskFunction | null,
|
|
193
|
+
): Generator<any, any, any> | AsyncGenerator<any, any, any>;
|
|
191
194
|
description?: string;
|
|
192
195
|
input?: Record<string, SchemaDefinition>;
|
|
193
196
|
mimeType?: string;
|
|
@@ -202,7 +205,10 @@ export type GeneratorToolHandler = {
|
|
|
202
205
|
* @returns Resource content
|
|
203
206
|
*/
|
|
204
207
|
export type ResourceHandler = {
|
|
205
|
-
(
|
|
208
|
+
(
|
|
209
|
+
params: Record<string, string>,
|
|
210
|
+
ask?: AskFunction | null,
|
|
211
|
+
): any | Promise<any>;
|
|
206
212
|
/** Resource name for display */
|
|
207
213
|
name?: string;
|
|
208
214
|
/** Resource description */
|
|
@@ -220,7 +226,14 @@ export type ResourceHandler = {
|
|
|
220
226
|
* @returns Prompt messages (string, conversation result, or message array)
|
|
221
227
|
*/
|
|
222
228
|
export type PromptHandler = {
|
|
223
|
-
(
|
|
229
|
+
(
|
|
230
|
+
args: Record<string, any>,
|
|
231
|
+
ask?: AskFunction | null,
|
|
232
|
+
):
|
|
233
|
+
| string
|
|
234
|
+
| ConversationResult
|
|
235
|
+
| Message[]
|
|
236
|
+
| Promise<string | ConversationResult | Message[]>;
|
|
224
237
|
/** Prompt description */
|
|
225
238
|
description?: string;
|
|
226
239
|
/** Input schema definition using T types */
|
|
@@ -291,7 +304,7 @@ export interface SamplingOptions {
|
|
|
291
304
|
* Represents a JSON Schema property with optional metadata.
|
|
292
305
|
*/
|
|
293
306
|
export interface SchemaDefinition {
|
|
294
|
-
type:
|
|
307
|
+
type: "string" | "number" | "boolean" | "array" | "object";
|
|
295
308
|
description?: string;
|
|
296
309
|
enum?: any[];
|
|
297
310
|
default?: any;
|
|
@@ -477,7 +490,7 @@ export const T: {
|
|
|
477
490
|
* ```
|
|
478
491
|
*/
|
|
479
492
|
export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
480
|
-
type:
|
|
493
|
+
type: "object";
|
|
481
494
|
properties: Record<string, SchemaDefinition>;
|
|
482
495
|
required?: string[];
|
|
483
496
|
};
|
|
@@ -491,7 +504,7 @@ export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
|
491
504
|
*/
|
|
492
505
|
export interface Message {
|
|
493
506
|
/** Message role */
|
|
494
|
-
role:
|
|
507
|
+
role: "user" | "assistant";
|
|
495
508
|
/** Message content */
|
|
496
509
|
content: TextContent | ImageContent | ResourceContent;
|
|
497
510
|
}
|
|
@@ -500,7 +513,7 @@ export interface Message {
|
|
|
500
513
|
* Text content type.
|
|
501
514
|
*/
|
|
502
515
|
export interface TextContent {
|
|
503
|
-
type:
|
|
516
|
+
type: "text";
|
|
504
517
|
text: string;
|
|
505
518
|
}
|
|
506
519
|
|
|
@@ -508,7 +521,7 @@ export interface TextContent {
|
|
|
508
521
|
* Image content type (base64-encoded).
|
|
509
522
|
*/
|
|
510
523
|
export interface ImageContent {
|
|
511
|
-
type:
|
|
524
|
+
type: "image";
|
|
512
525
|
data: string;
|
|
513
526
|
mimeType: string;
|
|
514
527
|
}
|
|
@@ -517,7 +530,7 @@ export interface ImageContent {
|
|
|
517
530
|
* Resource content type (embedded resource).
|
|
518
531
|
*/
|
|
519
532
|
export interface ResourceContent {
|
|
520
|
-
type:
|
|
533
|
+
type: "resource";
|
|
521
534
|
resource: {
|
|
522
535
|
uri: string;
|
|
523
536
|
text: string;
|
|
@@ -588,7 +601,7 @@ export interface ConversationHelpers {
|
|
|
588
601
|
* ```
|
|
589
602
|
*/
|
|
590
603
|
export function conversation(
|
|
591
|
-
builderFn: (helpers: ConversationHelpers) => Message[]
|
|
604
|
+
builderFn: (helpers: ConversationHelpers) => Message[],
|
|
592
605
|
): ConversationResult;
|
|
593
606
|
|
|
594
607
|
// ============================================================================
|
|
@@ -600,7 +613,7 @@ export function conversation(
|
|
|
600
613
|
* Yielded by generator tools to report progress.
|
|
601
614
|
*/
|
|
602
615
|
export interface ProgressNotification {
|
|
603
|
-
type:
|
|
616
|
+
type: "progress";
|
|
604
617
|
/** Current step number (1-indexed) */
|
|
605
618
|
progress: number;
|
|
606
619
|
/** Total steps (optional, for determinate progress) */
|
|
@@ -666,7 +679,15 @@ export const PROGRESS_DEFAULTS: {
|
|
|
666
679
|
/**
|
|
667
680
|
* Log severity level (RFC 5424).
|
|
668
681
|
*/
|
|
669
|
-
export type LogLevel =
|
|
682
|
+
export type LogLevel =
|
|
683
|
+
| "debug"
|
|
684
|
+
| "info"
|
|
685
|
+
| "notice"
|
|
686
|
+
| "warning"
|
|
687
|
+
| "error"
|
|
688
|
+
| "critical"
|
|
689
|
+
| "alert"
|
|
690
|
+
| "emergency";
|
|
670
691
|
|
|
671
692
|
/**
|
|
672
693
|
* Log object with methods for each severity level.
|
package/dist/security.js
CHANGED
|
@@ -187,18 +187,18 @@ export function validateStringInput(value, maxLength = 10485760) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
|
-
* Validate URI scheme against
|
|
190
|
+
* Validate URI scheme against denylist
|
|
191
191
|
*
|
|
192
192
|
* Security: Prevents injection attacks via dangerous URI schemes
|
|
193
|
-
* - Blocks file://, javascript:, data:,
|
|
194
|
-
* - Allows http
|
|
195
|
-
* -
|
|
193
|
+
* - Blocks file://, javascript:, data:, vbscript:, about: by default
|
|
194
|
+
* - Allows all other schemes (http://, https://, custom schemes like docs://)
|
|
195
|
+
* - MCP spec explicitly supports custom URI schemes for resources
|
|
196
|
+
* - Validates scheme format per RFC 3986
|
|
196
197
|
*
|
|
197
198
|
* @param {string} uri - URI to validate
|
|
198
|
-
* @
|
|
199
|
-
* @returns {boolean} True if URI scheme is allowed
|
|
199
|
+
* @returns {boolean} True if URI scheme is safe (not in denylist)
|
|
200
200
|
*/
|
|
201
|
-
export function validateUriScheme(uri
|
|
201
|
+
export function validateUriScheme(uri) {
|
|
202
202
|
if (!uri || typeof uri !== "string") return false;
|
|
203
203
|
|
|
204
204
|
// Extract scheme (characters before first colon)
|
|
@@ -207,13 +207,25 @@ export function validateUriScheme(uri, allowedSchemes = ["http", "https"]) {
|
|
|
207
207
|
|
|
208
208
|
const scheme = schemeMatch[1].toLowerCase();
|
|
209
209
|
|
|
210
|
+
// RFC 3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
|
211
|
+
// Must start with a letter, followed by letters, digits, +, -, or .
|
|
212
|
+
const schemeRegex = /^[a-z][a-z0-9+.-]*$/;
|
|
213
|
+
if (!schemeRegex.test(scheme)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Reasonable length limit to prevent abuse
|
|
218
|
+
if (scheme.length > 32) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
210
222
|
// Check if scheme is explicitly dangerous
|
|
211
223
|
if (DANGEROUS_SCHEMES.includes(scheme)) {
|
|
212
224
|
return false;
|
|
213
225
|
}
|
|
214
226
|
|
|
215
|
-
//
|
|
216
|
-
return
|
|
227
|
+
// All other schemes (including custom ones) are allowed
|
|
228
|
+
return true;
|
|
217
229
|
}
|
|
218
230
|
|
|
219
231
|
/**
|
package/dist/server.js
CHANGED
|
@@ -438,24 +438,31 @@ export function createServer(options = {}) {
|
|
|
438
438
|
// Validate URI scheme (prevent dangerous schemes like file://, javascript:, data:)
|
|
439
439
|
if (!validateUriScheme(uri)) {
|
|
440
440
|
throw new Error(
|
|
441
|
-
`
|
|
441
|
+
`Disallowed URI scheme: dangerous schemes like file://, javascript:, data: are not allowed`,
|
|
442
442
|
);
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
//
|
|
446
|
-
|
|
445
|
+
// Apply canonicalizePath to ALL URIs for consistent security validation
|
|
446
|
+
// This catches: null bytes, Unicode variants, multi-layer encoding, mixed encoding
|
|
447
|
+
// Path normalization (slash deduplication, backslash conversion) is safe for all schemes
|
|
448
|
+
const normalizedUri = canonicalizePath(uri);
|
|
447
449
|
|
|
448
450
|
// Find matching resource (try exact match first, then templates)
|
|
449
451
|
let handler = null;
|
|
450
452
|
let extractedParams = {};
|
|
451
453
|
|
|
452
|
-
// Try exact match first (use
|
|
453
|
-
if (resources.has(
|
|
454
|
-
handler = resources.get(
|
|
454
|
+
// Try exact match first (use normalized URI for matching)
|
|
455
|
+
if (resources.has(normalizedUri)) {
|
|
456
|
+
handler = resources.get(normalizedUri);
|
|
455
457
|
} else {
|
|
456
458
|
// Try template matching using uri.js module
|
|
457
459
|
for (const [registeredUri, h] of resources.entries()) {
|
|
458
|
-
|
|
460
|
+
// Normalize registered URI for consistent matching (slash deduplication only)
|
|
461
|
+
// Don't apply security validation to developer-provided templates
|
|
462
|
+
const normalizedRegisteredUri = registeredUri
|
|
463
|
+
.replace(/\\/g, "/") // Convert backslashes to forward slashes
|
|
464
|
+
.replace(/\/+/g, "/"); // Remove duplicate slashes
|
|
465
|
+
const match = matchUri(normalizedRegisteredUri, normalizedUri);
|
|
459
466
|
if (match) {
|
|
460
467
|
handler = h;
|
|
461
468
|
extractedParams = match.params || {};
|
|
@@ -783,11 +790,9 @@ export function createServer(options = {}) {
|
|
|
783
790
|
return handleLoggingSetLevel(params);
|
|
784
791
|
|
|
785
792
|
default: {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
throw error;
|
|
790
|
-
}
|
|
793
|
+
const error = new Error("Method not found");
|
|
794
|
+
error.code = -32601;
|
|
795
|
+
throw error;
|
|
791
796
|
}
|
|
792
797
|
}
|
|
793
798
|
}
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -187,7 +187,10 @@ export type ToolHandler = {
|
|
|
187
187
|
* ```
|
|
188
188
|
*/
|
|
189
189
|
export type GeneratorToolHandler = {
|
|
190
|
-
(
|
|
190
|
+
(
|
|
191
|
+
args: Record<string, any>,
|
|
192
|
+
ask?: AskFunction | null,
|
|
193
|
+
): Generator<any, any, any> | AsyncGenerator<any, any, any>;
|
|
191
194
|
description?: string;
|
|
192
195
|
input?: Record<string, SchemaDefinition>;
|
|
193
196
|
mimeType?: string;
|
|
@@ -202,7 +205,10 @@ export type GeneratorToolHandler = {
|
|
|
202
205
|
* @returns Resource content
|
|
203
206
|
*/
|
|
204
207
|
export type ResourceHandler = {
|
|
205
|
-
(
|
|
208
|
+
(
|
|
209
|
+
params: Record<string, string>,
|
|
210
|
+
ask?: AskFunction | null,
|
|
211
|
+
): any | Promise<any>;
|
|
206
212
|
/** Resource name for display */
|
|
207
213
|
name?: string;
|
|
208
214
|
/** Resource description */
|
|
@@ -220,7 +226,14 @@ export type ResourceHandler = {
|
|
|
220
226
|
* @returns Prompt messages (string, conversation result, or message array)
|
|
221
227
|
*/
|
|
222
228
|
export type PromptHandler = {
|
|
223
|
-
(
|
|
229
|
+
(
|
|
230
|
+
args: Record<string, any>,
|
|
231
|
+
ask?: AskFunction | null,
|
|
232
|
+
):
|
|
233
|
+
| string
|
|
234
|
+
| ConversationResult
|
|
235
|
+
| Message[]
|
|
236
|
+
| Promise<string | ConversationResult | Message[]>;
|
|
224
237
|
/** Prompt description */
|
|
225
238
|
description?: string;
|
|
226
239
|
/** Input schema definition using T types */
|
|
@@ -291,7 +304,7 @@ export interface SamplingOptions {
|
|
|
291
304
|
* Represents a JSON Schema property with optional metadata.
|
|
292
305
|
*/
|
|
293
306
|
export interface SchemaDefinition {
|
|
294
|
-
type:
|
|
307
|
+
type: "string" | "number" | "boolean" | "array" | "object";
|
|
295
308
|
description?: string;
|
|
296
309
|
enum?: any[];
|
|
297
310
|
default?: any;
|
|
@@ -477,7 +490,7 @@ export const T: {
|
|
|
477
490
|
* ```
|
|
478
491
|
*/
|
|
479
492
|
export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
480
|
-
type:
|
|
493
|
+
type: "object";
|
|
481
494
|
properties: Record<string, SchemaDefinition>;
|
|
482
495
|
required?: string[];
|
|
483
496
|
};
|
|
@@ -491,7 +504,7 @@ export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
|
491
504
|
*/
|
|
492
505
|
export interface Message {
|
|
493
506
|
/** Message role */
|
|
494
|
-
role:
|
|
507
|
+
role: "user" | "assistant";
|
|
495
508
|
/** Message content */
|
|
496
509
|
content: TextContent | ImageContent | ResourceContent;
|
|
497
510
|
}
|
|
@@ -500,7 +513,7 @@ export interface Message {
|
|
|
500
513
|
* Text content type.
|
|
501
514
|
*/
|
|
502
515
|
export interface TextContent {
|
|
503
|
-
type:
|
|
516
|
+
type: "text";
|
|
504
517
|
text: string;
|
|
505
518
|
}
|
|
506
519
|
|
|
@@ -508,7 +521,7 @@ export interface TextContent {
|
|
|
508
521
|
* Image content type (base64-encoded).
|
|
509
522
|
*/
|
|
510
523
|
export interface ImageContent {
|
|
511
|
-
type:
|
|
524
|
+
type: "image";
|
|
512
525
|
data: string;
|
|
513
526
|
mimeType: string;
|
|
514
527
|
}
|
|
@@ -517,7 +530,7 @@ export interface ImageContent {
|
|
|
517
530
|
* Resource content type (embedded resource).
|
|
518
531
|
*/
|
|
519
532
|
export interface ResourceContent {
|
|
520
|
-
type:
|
|
533
|
+
type: "resource";
|
|
521
534
|
resource: {
|
|
522
535
|
uri: string;
|
|
523
536
|
text: string;
|
|
@@ -588,7 +601,7 @@ export interface ConversationHelpers {
|
|
|
588
601
|
* ```
|
|
589
602
|
*/
|
|
590
603
|
export function conversation(
|
|
591
|
-
builderFn: (helpers: ConversationHelpers) => Message[]
|
|
604
|
+
builderFn: (helpers: ConversationHelpers) => Message[],
|
|
592
605
|
): ConversationResult;
|
|
593
606
|
|
|
594
607
|
// ============================================================================
|
|
@@ -600,7 +613,7 @@ export function conversation(
|
|
|
600
613
|
* Yielded by generator tools to report progress.
|
|
601
614
|
*/
|
|
602
615
|
export interface ProgressNotification {
|
|
603
|
-
type:
|
|
616
|
+
type: "progress";
|
|
604
617
|
/** Current step number (1-indexed) */
|
|
605
618
|
progress: number;
|
|
606
619
|
/** Total steps (optional, for determinate progress) */
|
|
@@ -666,7 +679,15 @@ export const PROGRESS_DEFAULTS: {
|
|
|
666
679
|
/**
|
|
667
680
|
* Log severity level (RFC 5424).
|
|
668
681
|
*/
|
|
669
|
-
export type LogLevel =
|
|
682
|
+
export type LogLevel =
|
|
683
|
+
| "debug"
|
|
684
|
+
| "info"
|
|
685
|
+
| "notice"
|
|
686
|
+
| "warning"
|
|
687
|
+
| "error"
|
|
688
|
+
| "critical"
|
|
689
|
+
| "alert"
|
|
690
|
+
| "emergency";
|
|
670
691
|
|
|
671
692
|
/**
|
|
672
693
|
* Log object with methods for each severity level.
|
package/src/security.js
CHANGED
|
@@ -187,18 +187,18 @@ export function validateStringInput(value, maxLength = 10485760) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
|
-
* Validate URI scheme against
|
|
190
|
+
* Validate URI scheme against denylist
|
|
191
191
|
*
|
|
192
192
|
* Security: Prevents injection attacks via dangerous URI schemes
|
|
193
|
-
* - Blocks file://, javascript:, data:,
|
|
194
|
-
* - Allows http
|
|
195
|
-
* -
|
|
193
|
+
* - Blocks file://, javascript:, data:, vbscript:, about: by default
|
|
194
|
+
* - Allows all other schemes (http://, https://, custom schemes like docs://)
|
|
195
|
+
* - MCP spec explicitly supports custom URI schemes for resources
|
|
196
|
+
* - Validates scheme format per RFC 3986
|
|
196
197
|
*
|
|
197
198
|
* @param {string} uri - URI to validate
|
|
198
|
-
* @
|
|
199
|
-
* @returns {boolean} True if URI scheme is allowed
|
|
199
|
+
* @returns {boolean} True if URI scheme is safe (not in denylist)
|
|
200
200
|
*/
|
|
201
|
-
export function validateUriScheme(uri
|
|
201
|
+
export function validateUriScheme(uri) {
|
|
202
202
|
if (!uri || typeof uri !== "string") return false;
|
|
203
203
|
|
|
204
204
|
// Extract scheme (characters before first colon)
|
|
@@ -207,13 +207,25 @@ export function validateUriScheme(uri, allowedSchemes = ["http", "https"]) {
|
|
|
207
207
|
|
|
208
208
|
const scheme = schemeMatch[1].toLowerCase();
|
|
209
209
|
|
|
210
|
+
// RFC 3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
|
211
|
+
// Must start with a letter, followed by letters, digits, +, -, or .
|
|
212
|
+
const schemeRegex = /^[a-z][a-z0-9+.-]*$/;
|
|
213
|
+
if (!schemeRegex.test(scheme)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Reasonable length limit to prevent abuse
|
|
218
|
+
if (scheme.length > 32) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
210
222
|
// Check if scheme is explicitly dangerous
|
|
211
223
|
if (DANGEROUS_SCHEMES.includes(scheme)) {
|
|
212
224
|
return false;
|
|
213
225
|
}
|
|
214
226
|
|
|
215
|
-
//
|
|
216
|
-
return
|
|
227
|
+
// All other schemes (including custom ones) are allowed
|
|
228
|
+
return true;
|
|
217
229
|
}
|
|
218
230
|
|
|
219
231
|
/**
|
package/src/server.js
CHANGED
|
@@ -438,24 +438,31 @@ export function createServer(options = {}) {
|
|
|
438
438
|
// Validate URI scheme (prevent dangerous schemes like file://, javascript:, data:)
|
|
439
439
|
if (!validateUriScheme(uri)) {
|
|
440
440
|
throw new Error(
|
|
441
|
-
`
|
|
441
|
+
`Disallowed URI scheme: dangerous schemes like file://, javascript:, data: are not allowed`,
|
|
442
442
|
);
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
//
|
|
446
|
-
|
|
445
|
+
// Apply canonicalizePath to ALL URIs for consistent security validation
|
|
446
|
+
// This catches: null bytes, Unicode variants, multi-layer encoding, mixed encoding
|
|
447
|
+
// Path normalization (slash deduplication, backslash conversion) is safe for all schemes
|
|
448
|
+
const normalizedUri = canonicalizePath(uri);
|
|
447
449
|
|
|
448
450
|
// Find matching resource (try exact match first, then templates)
|
|
449
451
|
let handler = null;
|
|
450
452
|
let extractedParams = {};
|
|
451
453
|
|
|
452
|
-
// Try exact match first (use
|
|
453
|
-
if (resources.has(
|
|
454
|
-
handler = resources.get(
|
|
454
|
+
// Try exact match first (use normalized URI for matching)
|
|
455
|
+
if (resources.has(normalizedUri)) {
|
|
456
|
+
handler = resources.get(normalizedUri);
|
|
455
457
|
} else {
|
|
456
458
|
// Try template matching using uri.js module
|
|
457
459
|
for (const [registeredUri, h] of resources.entries()) {
|
|
458
|
-
|
|
460
|
+
// Normalize registered URI for consistent matching (slash deduplication only)
|
|
461
|
+
// Don't apply security validation to developer-provided templates
|
|
462
|
+
const normalizedRegisteredUri = registeredUri
|
|
463
|
+
.replace(/\\/g, "/") // Convert backslashes to forward slashes
|
|
464
|
+
.replace(/\/+/g, "/"); // Remove duplicate slashes
|
|
465
|
+
const match = matchUri(normalizedRegisteredUri, normalizedUri);
|
|
459
466
|
if (match) {
|
|
460
467
|
handler = h;
|
|
461
468
|
extractedParams = match.params || {};
|
|
@@ -783,11 +790,9 @@ export function createServer(options = {}) {
|
|
|
783
790
|
return handleLoggingSetLevel(params);
|
|
784
791
|
|
|
785
792
|
default: {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
throw error;
|
|
790
|
-
}
|
|
793
|
+
const error = new Error("Method not found");
|
|
794
|
+
error.code = -32601;
|
|
795
|
+
throw error;
|
|
791
796
|
}
|
|
792
797
|
}
|
|
793
798
|
}
|