@mctx-ai/mcp-server 0.4.0 → 0.5.1
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 +52 -14
- package/dist/security.js +21 -9
- package/dist/server.js +78 -8
- package/package.json +1 -1
- package/src/index.d.ts +52 -14
- package/src/security.js +21 -9
- package/src/server.js +78 -8
package/dist/index.d.ts
CHANGED
|
@@ -106,16 +106,33 @@ export interface Server {
|
|
|
106
106
|
fetch(request: Request, env?: any, ctx?: any): Promise<Response>;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Server configuration options.
|
|
111
|
+
*/
|
|
112
|
+
export interface ServerOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Instructions for LLM clients using this server.
|
|
115
|
+
* Helps guide the LLM on how to use the server's capabilities.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* "This server provides tools for managing customer data. Use list_customers to browse, get_customer to fetch details."
|
|
119
|
+
*/
|
|
120
|
+
instructions?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
109
123
|
/**
|
|
110
124
|
* Creates an MCP server instance.
|
|
111
125
|
*
|
|
126
|
+
* @param options - Server configuration options
|
|
112
127
|
* @returns Server instance with registration methods and fetch handler
|
|
113
128
|
*
|
|
114
129
|
* @example
|
|
115
130
|
* ```typescript
|
|
116
131
|
* import { createServer, T } from '@mctx-ai/mcp-server';
|
|
117
132
|
*
|
|
118
|
-
* const server = createServer(
|
|
133
|
+
* const server = createServer({
|
|
134
|
+
* instructions: "You help developers debug CI pipelines..."
|
|
135
|
+
* });
|
|
119
136
|
*
|
|
120
137
|
* server.tool('greet', (args: { name: string }) => {
|
|
121
138
|
* return `Hello, ${args.name}!`;
|
|
@@ -124,7 +141,7 @@ export interface Server {
|
|
|
124
141
|
* export default { fetch: server.fetch };
|
|
125
142
|
* ```
|
|
126
143
|
*/
|
|
127
|
-
export function createServer(): Server;
|
|
144
|
+
export function createServer(options?: ServerOptions): Server;
|
|
128
145
|
|
|
129
146
|
// ============================================================================
|
|
130
147
|
// Handler Types
|
|
@@ -170,7 +187,10 @@ export type ToolHandler = {
|
|
|
170
187
|
* ```
|
|
171
188
|
*/
|
|
172
189
|
export type GeneratorToolHandler = {
|
|
173
|
-
(
|
|
190
|
+
(
|
|
191
|
+
args: Record<string, any>,
|
|
192
|
+
ask?: AskFunction | null,
|
|
193
|
+
): Generator<any, any, any> | AsyncGenerator<any, any, any>;
|
|
174
194
|
description?: string;
|
|
175
195
|
input?: Record<string, SchemaDefinition>;
|
|
176
196
|
mimeType?: string;
|
|
@@ -185,7 +205,10 @@ export type GeneratorToolHandler = {
|
|
|
185
205
|
* @returns Resource content
|
|
186
206
|
*/
|
|
187
207
|
export type ResourceHandler = {
|
|
188
|
-
(
|
|
208
|
+
(
|
|
209
|
+
params: Record<string, string>,
|
|
210
|
+
ask?: AskFunction | null,
|
|
211
|
+
): any | Promise<any>;
|
|
189
212
|
/** Resource name for display */
|
|
190
213
|
name?: string;
|
|
191
214
|
/** Resource description */
|
|
@@ -203,7 +226,14 @@ export type ResourceHandler = {
|
|
|
203
226
|
* @returns Prompt messages (string, conversation result, or message array)
|
|
204
227
|
*/
|
|
205
228
|
export type PromptHandler = {
|
|
206
|
-
(
|
|
229
|
+
(
|
|
230
|
+
args: Record<string, any>,
|
|
231
|
+
ask?: AskFunction | null,
|
|
232
|
+
):
|
|
233
|
+
| string
|
|
234
|
+
| ConversationResult
|
|
235
|
+
| Message[]
|
|
236
|
+
| Promise<string | ConversationResult | Message[]>;
|
|
207
237
|
/** Prompt description */
|
|
208
238
|
description?: string;
|
|
209
239
|
/** Input schema definition using T types */
|
|
@@ -274,7 +304,7 @@ export interface SamplingOptions {
|
|
|
274
304
|
* Represents a JSON Schema property with optional metadata.
|
|
275
305
|
*/
|
|
276
306
|
export interface SchemaDefinition {
|
|
277
|
-
type:
|
|
307
|
+
type: "string" | "number" | "boolean" | "array" | "object";
|
|
278
308
|
description?: string;
|
|
279
309
|
enum?: any[];
|
|
280
310
|
default?: any;
|
|
@@ -460,7 +490,7 @@ export const T: {
|
|
|
460
490
|
* ```
|
|
461
491
|
*/
|
|
462
492
|
export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
463
|
-
type:
|
|
493
|
+
type: "object";
|
|
464
494
|
properties: Record<string, SchemaDefinition>;
|
|
465
495
|
required?: string[];
|
|
466
496
|
};
|
|
@@ -474,7 +504,7 @@ export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
|
474
504
|
*/
|
|
475
505
|
export interface Message {
|
|
476
506
|
/** Message role */
|
|
477
|
-
role:
|
|
507
|
+
role: "user" | "assistant";
|
|
478
508
|
/** Message content */
|
|
479
509
|
content: TextContent | ImageContent | ResourceContent;
|
|
480
510
|
}
|
|
@@ -483,7 +513,7 @@ export interface Message {
|
|
|
483
513
|
* Text content type.
|
|
484
514
|
*/
|
|
485
515
|
export interface TextContent {
|
|
486
|
-
type:
|
|
516
|
+
type: "text";
|
|
487
517
|
text: string;
|
|
488
518
|
}
|
|
489
519
|
|
|
@@ -491,7 +521,7 @@ export interface TextContent {
|
|
|
491
521
|
* Image content type (base64-encoded).
|
|
492
522
|
*/
|
|
493
523
|
export interface ImageContent {
|
|
494
|
-
type:
|
|
524
|
+
type: "image";
|
|
495
525
|
data: string;
|
|
496
526
|
mimeType: string;
|
|
497
527
|
}
|
|
@@ -500,7 +530,7 @@ export interface ImageContent {
|
|
|
500
530
|
* Resource content type (embedded resource).
|
|
501
531
|
*/
|
|
502
532
|
export interface ResourceContent {
|
|
503
|
-
type:
|
|
533
|
+
type: "resource";
|
|
504
534
|
resource: {
|
|
505
535
|
uri: string;
|
|
506
536
|
text: string;
|
|
@@ -571,7 +601,7 @@ export interface ConversationHelpers {
|
|
|
571
601
|
* ```
|
|
572
602
|
*/
|
|
573
603
|
export function conversation(
|
|
574
|
-
builderFn: (helpers: ConversationHelpers) => Message[]
|
|
604
|
+
builderFn: (helpers: ConversationHelpers) => Message[],
|
|
575
605
|
): ConversationResult;
|
|
576
606
|
|
|
577
607
|
// ============================================================================
|
|
@@ -583,7 +613,7 @@ export function conversation(
|
|
|
583
613
|
* Yielded by generator tools to report progress.
|
|
584
614
|
*/
|
|
585
615
|
export interface ProgressNotification {
|
|
586
|
-
type:
|
|
616
|
+
type: "progress";
|
|
587
617
|
/** Current step number (1-indexed) */
|
|
588
618
|
progress: number;
|
|
589
619
|
/** Total steps (optional, for determinate progress) */
|
|
@@ -649,7 +679,15 @@ export const PROGRESS_DEFAULTS: {
|
|
|
649
679
|
/**
|
|
650
680
|
* Log severity level (RFC 5424).
|
|
651
681
|
*/
|
|
652
|
-
export type LogLevel =
|
|
682
|
+
export type LogLevel =
|
|
683
|
+
| "debug"
|
|
684
|
+
| "info"
|
|
685
|
+
| "notice"
|
|
686
|
+
| "warning"
|
|
687
|
+
| "error"
|
|
688
|
+
| "critical"
|
|
689
|
+
| "alert"
|
|
690
|
+
| "emergency";
|
|
653
691
|
|
|
654
692
|
/**
|
|
655
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
|
@@ -132,9 +132,13 @@ function paginate(items, cursor, pageSize = 50) {
|
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Create MCP server instance
|
|
135
|
+
* @param {Object} [options] - Server configuration options
|
|
136
|
+
* @param {string} [options.instructions] - Server instructions for LLM clients
|
|
135
137
|
* @returns {Object} Server app with registration methods and fetch handler
|
|
136
138
|
*/
|
|
137
|
-
export function createServer() {
|
|
139
|
+
export function createServer(options = {}) {
|
|
140
|
+
const { instructions } = options;
|
|
141
|
+
|
|
138
142
|
// Internal registries
|
|
139
143
|
const tools = new Map();
|
|
140
144
|
const resources = new Map();
|
|
@@ -434,24 +438,30 @@ export function createServer() {
|
|
|
434
438
|
// Validate URI scheme (prevent dangerous schemes like file://, javascript:, data:)
|
|
435
439
|
if (!validateUriScheme(uri)) {
|
|
436
440
|
throw new Error(
|
|
437
|
-
`
|
|
441
|
+
`Disallowed URI scheme: dangerous schemes like file://, javascript:, data: are not allowed`,
|
|
438
442
|
);
|
|
439
443
|
}
|
|
440
444
|
|
|
441
|
-
//
|
|
442
|
-
|
|
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);
|
|
443
449
|
|
|
444
450
|
// Find matching resource (try exact match first, then templates)
|
|
445
451
|
let handler = null;
|
|
446
452
|
let extractedParams = {};
|
|
447
453
|
|
|
448
|
-
// Try exact match first (use
|
|
449
|
-
if (resources.has(
|
|
450
|
-
handler = resources.get(
|
|
454
|
+
// Try exact match first (use normalized URI for matching)
|
|
455
|
+
if (resources.has(normalizedUri)) {
|
|
456
|
+
handler = resources.get(normalizedUri);
|
|
451
457
|
} else {
|
|
452
458
|
// Try template matching using uri.js module
|
|
459
|
+
// Canonicalize registered URIs for consistent matching
|
|
453
460
|
for (const [registeredUri, h] of resources.entries()) {
|
|
454
|
-
|
|
461
|
+
// Canonicalize the registered URI for comparison
|
|
462
|
+
// This ensures consistent matching regardless of registration format
|
|
463
|
+
const normalizedRegisteredUri = canonicalizePath(registeredUri);
|
|
464
|
+
const match = matchUri(normalizedRegisteredUri, normalizedUri);
|
|
455
465
|
if (match) {
|
|
456
466
|
handler = h;
|
|
457
467
|
extractedParams = match.params || {};
|
|
@@ -678,6 +688,55 @@ export function createServer() {
|
|
|
678
688
|
return {}; // Success
|
|
679
689
|
}
|
|
680
690
|
|
|
691
|
+
/**
|
|
692
|
+
* Handle initialize request
|
|
693
|
+
* Auto-detects capabilities from registered tools/resources/prompts
|
|
694
|
+
* @param {Object} _params - Initialize params (clientInfo, etc.)
|
|
695
|
+
* @returns {Object} Server capabilities and info
|
|
696
|
+
*/
|
|
697
|
+
function handleInitialize(_params) {
|
|
698
|
+
// Build capabilities object by detecting what's registered
|
|
699
|
+
const capabilities = {};
|
|
700
|
+
|
|
701
|
+
// Add tools capability if any tools are registered
|
|
702
|
+
if (tools.size > 0) {
|
|
703
|
+
capabilities.tools = {};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Add resources capability if any resources are registered
|
|
707
|
+
if (resources.size > 0) {
|
|
708
|
+
capabilities.resources = {
|
|
709
|
+
subscribe: false, // Resource subscriptions not supported yet
|
|
710
|
+
listChanged: false, // Resource change notifications not supported yet
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Add prompts capability if any prompts are registered
|
|
715
|
+
if (prompts.size > 0) {
|
|
716
|
+
capabilities.prompts = {};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Always include logging capability
|
|
720
|
+
capabilities.logging = {};
|
|
721
|
+
|
|
722
|
+
// Build response
|
|
723
|
+
const response = {
|
|
724
|
+
protocolVersion: "2024-11-05",
|
|
725
|
+
capabilities,
|
|
726
|
+
serverInfo: {
|
|
727
|
+
name: "@mctx-ai/mcp-server",
|
|
728
|
+
version: "0.3.0",
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// Include instructions if provided
|
|
733
|
+
if (instructions) {
|
|
734
|
+
response.instructions = instructions;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return response;
|
|
738
|
+
}
|
|
739
|
+
|
|
681
740
|
/**
|
|
682
741
|
* Route JSON-RPC request to appropriate handler
|
|
683
742
|
* @param {Object} request - JSON-RPC request
|
|
@@ -687,6 +746,17 @@ export function createServer() {
|
|
|
687
746
|
const { method, params, _meta } = request;
|
|
688
747
|
|
|
689
748
|
switch (method) {
|
|
749
|
+
case "initialize":
|
|
750
|
+
return handleInitialize(params);
|
|
751
|
+
|
|
752
|
+
case "initialized":
|
|
753
|
+
// Notification - no response needed
|
|
754
|
+
return null;
|
|
755
|
+
|
|
756
|
+
case "ping":
|
|
757
|
+
// Respond to ping with empty result
|
|
758
|
+
return {};
|
|
759
|
+
|
|
690
760
|
case "tools/list":
|
|
691
761
|
return handleToolsList(params);
|
|
692
762
|
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -106,16 +106,33 @@ export interface Server {
|
|
|
106
106
|
fetch(request: Request, env?: any, ctx?: any): Promise<Response>;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Server configuration options.
|
|
111
|
+
*/
|
|
112
|
+
export interface ServerOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Instructions for LLM clients using this server.
|
|
115
|
+
* Helps guide the LLM on how to use the server's capabilities.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* "This server provides tools for managing customer data. Use list_customers to browse, get_customer to fetch details."
|
|
119
|
+
*/
|
|
120
|
+
instructions?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
109
123
|
/**
|
|
110
124
|
* Creates an MCP server instance.
|
|
111
125
|
*
|
|
126
|
+
* @param options - Server configuration options
|
|
112
127
|
* @returns Server instance with registration methods and fetch handler
|
|
113
128
|
*
|
|
114
129
|
* @example
|
|
115
130
|
* ```typescript
|
|
116
131
|
* import { createServer, T } from '@mctx-ai/mcp-server';
|
|
117
132
|
*
|
|
118
|
-
* const server = createServer(
|
|
133
|
+
* const server = createServer({
|
|
134
|
+
* instructions: "You help developers debug CI pipelines..."
|
|
135
|
+
* });
|
|
119
136
|
*
|
|
120
137
|
* server.tool('greet', (args: { name: string }) => {
|
|
121
138
|
* return `Hello, ${args.name}!`;
|
|
@@ -124,7 +141,7 @@ export interface Server {
|
|
|
124
141
|
* export default { fetch: server.fetch };
|
|
125
142
|
* ```
|
|
126
143
|
*/
|
|
127
|
-
export function createServer(): Server;
|
|
144
|
+
export function createServer(options?: ServerOptions): Server;
|
|
128
145
|
|
|
129
146
|
// ============================================================================
|
|
130
147
|
// Handler Types
|
|
@@ -170,7 +187,10 @@ export type ToolHandler = {
|
|
|
170
187
|
* ```
|
|
171
188
|
*/
|
|
172
189
|
export type GeneratorToolHandler = {
|
|
173
|
-
(
|
|
190
|
+
(
|
|
191
|
+
args: Record<string, any>,
|
|
192
|
+
ask?: AskFunction | null,
|
|
193
|
+
): Generator<any, any, any> | AsyncGenerator<any, any, any>;
|
|
174
194
|
description?: string;
|
|
175
195
|
input?: Record<string, SchemaDefinition>;
|
|
176
196
|
mimeType?: string;
|
|
@@ -185,7 +205,10 @@ export type GeneratorToolHandler = {
|
|
|
185
205
|
* @returns Resource content
|
|
186
206
|
*/
|
|
187
207
|
export type ResourceHandler = {
|
|
188
|
-
(
|
|
208
|
+
(
|
|
209
|
+
params: Record<string, string>,
|
|
210
|
+
ask?: AskFunction | null,
|
|
211
|
+
): any | Promise<any>;
|
|
189
212
|
/** Resource name for display */
|
|
190
213
|
name?: string;
|
|
191
214
|
/** Resource description */
|
|
@@ -203,7 +226,14 @@ export type ResourceHandler = {
|
|
|
203
226
|
* @returns Prompt messages (string, conversation result, or message array)
|
|
204
227
|
*/
|
|
205
228
|
export type PromptHandler = {
|
|
206
|
-
(
|
|
229
|
+
(
|
|
230
|
+
args: Record<string, any>,
|
|
231
|
+
ask?: AskFunction | null,
|
|
232
|
+
):
|
|
233
|
+
| string
|
|
234
|
+
| ConversationResult
|
|
235
|
+
| Message[]
|
|
236
|
+
| Promise<string | ConversationResult | Message[]>;
|
|
207
237
|
/** Prompt description */
|
|
208
238
|
description?: string;
|
|
209
239
|
/** Input schema definition using T types */
|
|
@@ -274,7 +304,7 @@ export interface SamplingOptions {
|
|
|
274
304
|
* Represents a JSON Schema property with optional metadata.
|
|
275
305
|
*/
|
|
276
306
|
export interface SchemaDefinition {
|
|
277
|
-
type:
|
|
307
|
+
type: "string" | "number" | "boolean" | "array" | "object";
|
|
278
308
|
description?: string;
|
|
279
309
|
enum?: any[];
|
|
280
310
|
default?: any;
|
|
@@ -460,7 +490,7 @@ export const T: {
|
|
|
460
490
|
* ```
|
|
461
491
|
*/
|
|
462
492
|
export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
463
|
-
type:
|
|
493
|
+
type: "object";
|
|
464
494
|
properties: Record<string, SchemaDefinition>;
|
|
465
495
|
required?: string[];
|
|
466
496
|
};
|
|
@@ -474,7 +504,7 @@ export function buildInputSchema(input?: Record<string, SchemaDefinition>): {
|
|
|
474
504
|
*/
|
|
475
505
|
export interface Message {
|
|
476
506
|
/** Message role */
|
|
477
|
-
role:
|
|
507
|
+
role: "user" | "assistant";
|
|
478
508
|
/** Message content */
|
|
479
509
|
content: TextContent | ImageContent | ResourceContent;
|
|
480
510
|
}
|
|
@@ -483,7 +513,7 @@ export interface Message {
|
|
|
483
513
|
* Text content type.
|
|
484
514
|
*/
|
|
485
515
|
export interface TextContent {
|
|
486
|
-
type:
|
|
516
|
+
type: "text";
|
|
487
517
|
text: string;
|
|
488
518
|
}
|
|
489
519
|
|
|
@@ -491,7 +521,7 @@ export interface TextContent {
|
|
|
491
521
|
* Image content type (base64-encoded).
|
|
492
522
|
*/
|
|
493
523
|
export interface ImageContent {
|
|
494
|
-
type:
|
|
524
|
+
type: "image";
|
|
495
525
|
data: string;
|
|
496
526
|
mimeType: string;
|
|
497
527
|
}
|
|
@@ -500,7 +530,7 @@ export interface ImageContent {
|
|
|
500
530
|
* Resource content type (embedded resource).
|
|
501
531
|
*/
|
|
502
532
|
export interface ResourceContent {
|
|
503
|
-
type:
|
|
533
|
+
type: "resource";
|
|
504
534
|
resource: {
|
|
505
535
|
uri: string;
|
|
506
536
|
text: string;
|
|
@@ -571,7 +601,7 @@ export interface ConversationHelpers {
|
|
|
571
601
|
* ```
|
|
572
602
|
*/
|
|
573
603
|
export function conversation(
|
|
574
|
-
builderFn: (helpers: ConversationHelpers) => Message[]
|
|
604
|
+
builderFn: (helpers: ConversationHelpers) => Message[],
|
|
575
605
|
): ConversationResult;
|
|
576
606
|
|
|
577
607
|
// ============================================================================
|
|
@@ -583,7 +613,7 @@ export function conversation(
|
|
|
583
613
|
* Yielded by generator tools to report progress.
|
|
584
614
|
*/
|
|
585
615
|
export interface ProgressNotification {
|
|
586
|
-
type:
|
|
616
|
+
type: "progress";
|
|
587
617
|
/** Current step number (1-indexed) */
|
|
588
618
|
progress: number;
|
|
589
619
|
/** Total steps (optional, for determinate progress) */
|
|
@@ -649,7 +679,15 @@ export const PROGRESS_DEFAULTS: {
|
|
|
649
679
|
/**
|
|
650
680
|
* Log severity level (RFC 5424).
|
|
651
681
|
*/
|
|
652
|
-
export type LogLevel =
|
|
682
|
+
export type LogLevel =
|
|
683
|
+
| "debug"
|
|
684
|
+
| "info"
|
|
685
|
+
| "notice"
|
|
686
|
+
| "warning"
|
|
687
|
+
| "error"
|
|
688
|
+
| "critical"
|
|
689
|
+
| "alert"
|
|
690
|
+
| "emergency";
|
|
653
691
|
|
|
654
692
|
/**
|
|
655
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
|
@@ -132,9 +132,13 @@ function paginate(items, cursor, pageSize = 50) {
|
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Create MCP server instance
|
|
135
|
+
* @param {Object} [options] - Server configuration options
|
|
136
|
+
* @param {string} [options.instructions] - Server instructions for LLM clients
|
|
135
137
|
* @returns {Object} Server app with registration methods and fetch handler
|
|
136
138
|
*/
|
|
137
|
-
export function createServer() {
|
|
139
|
+
export function createServer(options = {}) {
|
|
140
|
+
const { instructions } = options;
|
|
141
|
+
|
|
138
142
|
// Internal registries
|
|
139
143
|
const tools = new Map();
|
|
140
144
|
const resources = new Map();
|
|
@@ -434,24 +438,30 @@ export function createServer() {
|
|
|
434
438
|
// Validate URI scheme (prevent dangerous schemes like file://, javascript:, data:)
|
|
435
439
|
if (!validateUriScheme(uri)) {
|
|
436
440
|
throw new Error(
|
|
437
|
-
`
|
|
441
|
+
`Disallowed URI scheme: dangerous schemes like file://, javascript:, data: are not allowed`,
|
|
438
442
|
);
|
|
439
443
|
}
|
|
440
444
|
|
|
441
|
-
//
|
|
442
|
-
|
|
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);
|
|
443
449
|
|
|
444
450
|
// Find matching resource (try exact match first, then templates)
|
|
445
451
|
let handler = null;
|
|
446
452
|
let extractedParams = {};
|
|
447
453
|
|
|
448
|
-
// Try exact match first (use
|
|
449
|
-
if (resources.has(
|
|
450
|
-
handler = resources.get(
|
|
454
|
+
// Try exact match first (use normalized URI for matching)
|
|
455
|
+
if (resources.has(normalizedUri)) {
|
|
456
|
+
handler = resources.get(normalizedUri);
|
|
451
457
|
} else {
|
|
452
458
|
// Try template matching using uri.js module
|
|
459
|
+
// Canonicalize registered URIs for consistent matching
|
|
453
460
|
for (const [registeredUri, h] of resources.entries()) {
|
|
454
|
-
|
|
461
|
+
// Canonicalize the registered URI for comparison
|
|
462
|
+
// This ensures consistent matching regardless of registration format
|
|
463
|
+
const normalizedRegisteredUri = canonicalizePath(registeredUri);
|
|
464
|
+
const match = matchUri(normalizedRegisteredUri, normalizedUri);
|
|
455
465
|
if (match) {
|
|
456
466
|
handler = h;
|
|
457
467
|
extractedParams = match.params || {};
|
|
@@ -678,6 +688,55 @@ export function createServer() {
|
|
|
678
688
|
return {}; // Success
|
|
679
689
|
}
|
|
680
690
|
|
|
691
|
+
/**
|
|
692
|
+
* Handle initialize request
|
|
693
|
+
* Auto-detects capabilities from registered tools/resources/prompts
|
|
694
|
+
* @param {Object} _params - Initialize params (clientInfo, etc.)
|
|
695
|
+
* @returns {Object} Server capabilities and info
|
|
696
|
+
*/
|
|
697
|
+
function handleInitialize(_params) {
|
|
698
|
+
// Build capabilities object by detecting what's registered
|
|
699
|
+
const capabilities = {};
|
|
700
|
+
|
|
701
|
+
// Add tools capability if any tools are registered
|
|
702
|
+
if (tools.size > 0) {
|
|
703
|
+
capabilities.tools = {};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Add resources capability if any resources are registered
|
|
707
|
+
if (resources.size > 0) {
|
|
708
|
+
capabilities.resources = {
|
|
709
|
+
subscribe: false, // Resource subscriptions not supported yet
|
|
710
|
+
listChanged: false, // Resource change notifications not supported yet
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Add prompts capability if any prompts are registered
|
|
715
|
+
if (prompts.size > 0) {
|
|
716
|
+
capabilities.prompts = {};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Always include logging capability
|
|
720
|
+
capabilities.logging = {};
|
|
721
|
+
|
|
722
|
+
// Build response
|
|
723
|
+
const response = {
|
|
724
|
+
protocolVersion: "2024-11-05",
|
|
725
|
+
capabilities,
|
|
726
|
+
serverInfo: {
|
|
727
|
+
name: "@mctx-ai/mcp-server",
|
|
728
|
+
version: "0.3.0",
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// Include instructions if provided
|
|
733
|
+
if (instructions) {
|
|
734
|
+
response.instructions = instructions;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return response;
|
|
738
|
+
}
|
|
739
|
+
|
|
681
740
|
/**
|
|
682
741
|
* Route JSON-RPC request to appropriate handler
|
|
683
742
|
* @param {Object} request - JSON-RPC request
|
|
@@ -687,6 +746,17 @@ export function createServer() {
|
|
|
687
746
|
const { method, params, _meta } = request;
|
|
688
747
|
|
|
689
748
|
switch (method) {
|
|
749
|
+
case "initialize":
|
|
750
|
+
return handleInitialize(params);
|
|
751
|
+
|
|
752
|
+
case "initialized":
|
|
753
|
+
// Notification - no response needed
|
|
754
|
+
return null;
|
|
755
|
+
|
|
756
|
+
case "ping":
|
|
757
|
+
// Respond to ping with empty result
|
|
758
|
+
return {};
|
|
759
|
+
|
|
690
760
|
case "tools/list":
|
|
691
761
|
return handleToolsList(params);
|
|
692
762
|
|