@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 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
- (args: Record<string, any>, ask?: AskFunction | null): Generator<any, any, any> | AsyncGenerator<any, any, any>;
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
- (params: Record<string, string>, ask?: AskFunction | null): any | Promise<any>;
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
- (args: Record<string, any>, ask?: AskFunction | null): string | ConversationResult | Message[] | Promise<string | ConversationResult | Message[]>;
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: 'string' | 'number' | 'boolean' | 'array' | 'object';
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: 'object';
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: 'user' | 'assistant';
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: 'text';
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: 'image';
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: 'resource';
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: 'progress';
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 = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency';
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 allowlist
190
+ * Validate URI scheme against denylist
191
191
  *
192
192
  * Security: Prevents injection attacks via dangerous URI schemes
193
- * - Blocks file://, javascript:, data:, etc. by default
194
- * - Allows http:// and https:// by default
195
- * - Supports custom allowlists for special cases
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
- * @param {string[]} allowedSchemes - Allowed schemes (default: ['http', 'https'])
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, allowedSchemes = ["http", "https"]) {
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
- // Check if scheme is in allowlist
216
- return allowedSchemes.some((allowed) => allowed.toLowerCase() === scheme);
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
- `Invalid URI scheme: only http:// and https:// are allowed`,
441
+ `Disallowed URI scheme: dangerous schemes like file://, javascript:, data: are not allowed`,
438
442
  );
439
443
  }
440
444
 
441
- // Canonicalize path to prevent traversal attacks
442
- const canonicalUri = canonicalizePath(uri);
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 canonical URI for matching)
449
- if (resources.has(canonicalUri)) {
450
- handler = resources.get(canonicalUri);
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
- const match = matchUri(registeredUri, canonicalUri);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mctx-ai/mcp-server",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Build MCP servers with an Express-like API — no protocol knowledge required",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
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
- (args: Record<string, any>, ask?: AskFunction | null): Generator<any, any, any> | AsyncGenerator<any, any, any>;
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
- (params: Record<string, string>, ask?: AskFunction | null): any | Promise<any>;
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
- (args: Record<string, any>, ask?: AskFunction | null): string | ConversationResult | Message[] | Promise<string | ConversationResult | Message[]>;
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: 'string' | 'number' | 'boolean' | 'array' | 'object';
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: 'object';
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: 'user' | 'assistant';
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: 'text';
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: 'image';
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: 'resource';
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: 'progress';
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 = 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency';
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 allowlist
190
+ * Validate URI scheme against denylist
191
191
  *
192
192
  * Security: Prevents injection attacks via dangerous URI schemes
193
- * - Blocks file://, javascript:, data:, etc. by default
194
- * - Allows http:// and https:// by default
195
- * - Supports custom allowlists for special cases
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
- * @param {string[]} allowedSchemes - Allowed schemes (default: ['http', 'https'])
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, allowedSchemes = ["http", "https"]) {
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
- // Check if scheme is in allowlist
216
- return allowedSchemes.some((allowed) => allowed.toLowerCase() === scheme);
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
- `Invalid URI scheme: only http:// and https:// are allowed`,
441
+ `Disallowed URI scheme: dangerous schemes like file://, javascript:, data: are not allowed`,
438
442
  );
439
443
  }
440
444
 
441
- // Canonicalize path to prevent traversal attacks
442
- const canonicalUri = canonicalizePath(uri);
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 canonical URI for matching)
449
- if (resources.has(canonicalUri)) {
450
- handler = resources.get(canonicalUri);
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
- const match = matchUri(registeredUri, canonicalUri);
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