From 0bc9d6e30e886db7a88594b553cf5ad717aace69 Mon Sep 17 00:00:00 2001 From: ihrpr Date: Sun, 2 Nov 2025 16:56:28 +0000 Subject: [PATCH 1/5] spec for servers --- SERVER_REQUIREMENTS.md | 874 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 874 insertions(+) create mode 100644 SERVER_REQUIREMENTS.md diff --git a/SERVER_REQUIREMENTS.md b/SERVER_REQUIREMENTS.md new file mode 100644 index 0000000..e3f7501 --- /dev/null +++ b/SERVER_REQUIREMENTS.md @@ -0,0 +1,874 @@ +# MCP Server Conformance Requirements + +This document specifies the requirements for building an MCP "Everything Server" for conformance testing. SDK maintainers should implement a server meeting these requirements to enable automated conformance testing. + +## Purpose + +The Everything Server is a reference implementation that: +- Demonstrates all MCP server features in a single, testable server +- Uses standardized naming conventions for tools, resources, and prompts +- Enables automated conformance testing across different SDK implementations +- Serves as a working example for SDK users + +## Protocol Version + + +**Target MCP Specification**: `2025-06-18` + +## Transport + +**Required Transport**: Streamable HTTP (for initial conformance testing) + +## Specification Compliance + +This document specifies requirements based on the MCP specification. All features and behaviors described are mandated by the MCP specification itself and enable automated conformance testing. + +--- + +## Server Information + +### Server Identity + +Your server MUST provide: +```typescript +{ + name: "mcp-conformance-test-server", // Or your SDK-specific name + version: "1.0.0" +} +``` + +### Capabilities Declaration + +Your server MUST declare these capabilities during initialization: + +```typescript +{ + capabilities: { + tools: { + listChanged: true + }, + resources: { + subscribe: true, + listChanged: true + }, + prompts: { + listChanged: true + }, + logging: {}, + completions: {} + } +} +``` + +**Note**: All capabilities listed are required for conformance testing. The MCP specification also supports optional `experimental` capabilities for non-standard features, but these are not required for conformance. + +--- + +## 1. Lifecycle Requirements + +### 1.1. Initialize Handshake + +**Endpoint**: `initialize` + +**Requirements**: +- Accept `initialize` request with client info and capabilities +- Return server info, protocol version, and capabilities +- Protocol version MUST be `"2025-06-18"` + +**Example Response**: +```json +{ + "protocolVersion": "2025-06-18", + "serverInfo": { + "name": "mcp-conformance-test-server", + "version": "1.0.0" + }, + "capabilities": { /* as above */ } +} +``` + +### 1.2. Initialized Notification + +**Notification**: `initialized` + +**Requirements**: +- Accept `initialized` notification from client after handshake +- No response required (it's a notification) +- Server should be ready for requests after receiving this + +--- + +## 2. Tools Requirements + +### 2.1. List Tools + +**Endpoint**: `tools/list` + +**Requirements**: +- Return array of all available tools +- Each tool MUST have: + - `name` (string) + - `description` (string) + - `inputSchema` (valid JSON Schema object) + +### 2.2. Call Tool + +**Endpoint**: `tools/call` + +**Requirements**: +- Accept tool name and arguments +- Execute tool and return result +- Result MUST have `content` array +- Support `_meta.progressToken` for progress reporting (if provided) + +### 2.3. Required Tools + +Implement these tools with exact names: + +#### `test_simple_text` + +**Arguments**: None + +**Returns**: Text content +```typescript +{ + content: [ + { type: "text", text: "This is a simple text response for testing." } + ] +} +``` + +#### `test_image_content` + +**Arguments**: None + +**Returns**: Image content with base64 data +```typescript +{ + content: [ + { + type: "image", + data: "", + mimeType: "image/png" + } + ] +} +``` + +**Implementation Note**: Use a minimal test image (e.g., 1x1 red pixel PNG) + +#### `test_audio_content` + +**Arguments**: None + +**Returns**: Audio content with base64 data +```typescript +{ + content: [ + { + type: "audio", + data: "", + mimeType: "audio/wav" + } + ] +} +``` + +**Implementation Note**: Use a minimal test audio file + +#### `test_embedded_resource` + +**Arguments**: None + +**Returns**: Embedded resource content +```typescript +{ + content: [ + { + type: "resource", + resource: { + uri: "test://embedded-resource", + mimeType: "text/plain", + text: "This is an embedded resource content." + } + } + ] +} +``` + +#### `test_multiple_content_types` + +**Arguments**: None + +**Returns**: Multiple content items (text + image + resource) +```typescript +{ + content: [ + { type: "text", text: "Multiple content types test:" }, + { type: "image", data: "", mimeType: "image/png" }, + { + type: "resource", + resource: { + uri: "test://mixed-content-resource", + mimeType: "application/json", + text: "{\"test\":\"data\",\"value\":123}" + } + } + ] +} +``` + +#### `test_tool_with_logging` + +**Arguments**: None + +**Behavior**: During execution, send 3 log notifications at info level: +1. "Tool execution started" +2. "Tool processing data" (after ~50ms delay) +3. "Tool execution completed" (after another ~50ms delay) + +**Returns**: Text content confirming execution + +**Implementation Note**: The delays are important to test that clients can receive multiple log notifications during tool execution + +#### `test_tool_with_progress` + +**Arguments**: None + +**Behavior**: If `_meta.progressToken` is provided in request: +- Send progress notification: `0/100` +- Wait ~50ms +- Send progress notification: `50/100` +- Wait ~50ms +- Send progress notification: `100/100` + +If no progress token provided, just execute with delays. + +**Returns**: Text content confirming execution + +**Progress Notification Format**: +```typescript +{ + method: "notifications/progress", + params: { + progressToken: , + progress: 50, + total: 100 + } +} +``` + +#### `test_error_handling` + +**Arguments**: None + +**Behavior**: Always throw an error + +**Returns**: JSON-RPC error +```json +{ + "error": { + "code": -32000, + "message": "This tool intentionally returns an error for testing" + } +} +``` + +#### `test_sampling` + +**Arguments**: +- `prompt` (string, required) - The prompt to send to the LLM + +**Behavior**: Request LLM sampling from the client using `sampling/createMessage` + +**Sampling Request**: +```typescript +{ + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { "type": "text", "text": "" } + } + ], + "maxTokens": 100 + } +} +``` + +**Returns**: Text content with the LLM's response +```typescript +{ + "content": [ + { + "type": "text", + "text": "LLM response: " + } + ] +} +``` + +**Implementation Note**: If the client doesn't support sampling (no `sampling` capability), return an error. + +#### `test_elicitation` + +**Arguments**: +- `message` (string, required) - The message to show the user + +**Behavior**: Request user input from the client using `elicitation/create` + +**Elicitation Request**: +```typescript +{ + "method": "elicitation/create", + "params": { + "message": "", + "requestedSchema": { + "type": "object", + "properties": { + "response": { + "type": "string", + "description": "User's response" + } + }, + "required": ["response"] + } + } +} +``` + +**Returns**: Text content with the user's response +```typescript +{ + "content": [ + { + "type": "text", + "text": "User response: " + } + ] +} +``` + +**Implementation Note**: If the client doesn't support elicitation (no `elicitation` capability), return an error. + +--- + +## 3. Resources Requirements + +### 3.1. List Resources + +**Endpoint**: `resources/list` + +**Requirements**: +- Return array of all available **direct resources** (not templates) +- Support optional cursor-based pagination (see Section 6.1) +- Each resource MUST have: + - `uri` (string) + - `name` (string) + - `description` (string) + - `mimeType` (string, optional) + +**Note**: Resource templates are listed via separate `resources/templates/list` endpoint (see 3.1a) + +### 3.1a. List Resource Templates + +**Endpoint**: `resources/templates/list` + +**Requirements**: +- Return array of all available **resource templates** +- Support optional cursor-based pagination (see Section 6.1) +- Each template MUST have: + - `uriTemplate` (string) - RFC 6570 URI template + - `name` (string) + - `description` (string) + - `mimeType` (string, optional) + +### 3.2. Read Resource + +**Endpoint**: `resources/read` + +**Requirements**: +- Accept resource URI +- Return resource contents +- Response MUST have `contents` array +- Each content item MUST have `uri`, `mimeType`, and either `text` or `blob` + +### 3.3. Required Resources + +Implement these resources with exact URIs: + +#### `test://static-text` + +**Type**: Static text resource + +**Metadata** (for `resources/list`): +```typescript +{ + uri: "test://static-text", + name: "Static Text Resource", + description: "A static text resource for testing", + mimeType: "text/plain" +} +``` + +**Content** (for `resources/read`): +```typescript +{ + contents: [ + { + uri: "test://static-text", + mimeType: "text/plain", + text: "This is the content of the static text resource." + } + ] +} +``` + +#### `test://static-binary` + +**Type**: Static binary resource (image) + +**Metadata**: +```typescript +{ + uri: "test://static-binary", + name: "Static Binary Resource", + description: "A static binary resource (image) for testing", + mimeType: "image/png" +} +``` + +**Content**: +```typescript +{ + contents: [ + { + uri: "test://static-binary", + mimeType: "image/png", + blob: "" + } + ] +} +``` + +#### `test://template/{id}/data` + +**Type**: Resource template with parameter + +**Metadata** (for `resources/list`): +```typescript +{ + uriTemplate: "test://template/{id}/data", + name: "Resource Template", + description: "A resource template with parameter substitution", + mimeType: "application/json" +} +``` + +**Behavior**: When client requests `test://template/123/data`, substitute `{id}` with `123` + +**Content** (for `resources/read` with `uri: "test://template/123/data"`): +```typescript +{ + contents: [ + { + uri: "test://template/123/data", + mimeType: "application/json", + text: "{\"id\":\"123\",\"templateTest\":true,\"data\":\"Data for ID: 123\"}" + } + ] +} +``` + +**Implementation Note**: Use RFC 6570 URI template syntax + +#### `test://watched-resource` + +**Type**: Subscribable resource + +**Metadata**: +```typescript +{ + uri: "test://watched-resource", + name: "Watched Resource", + description: "A resource that can be subscribed to", + mimeType: "text/plain" +} +``` + +**Content**: +```typescript +{ + contents: [ + { + uri: "test://watched-resource", + mimeType: "text/plain", + text: "Watched resource content" + } + ] +} +``` + +### 3.4. Resource Subscription + +**Endpoint**: `resources/subscribe` + +**Requirements**: +- Accept subscription request with URI +- Track subscribed URIs +- Send `notifications/resources/updated` when subscribed resources change +- Return empty object `{}` + +**Example Request**: +```json +{ + "method": "resources/subscribe", + "params": { + "uri": "test://watched-resource" + } +} +``` + +**Endpoint**: `resources/unsubscribe` + +**Requirements**: +- Accept unsubscribe request with URI +- Remove URI from subscriptions +- Stop sending update notifications for that URI +- Return empty object `{}` + +--- + +## 4. Prompts Requirements + +### 4.1. List Prompts + +**Endpoint**: `prompts/list` + +**Requirements**: +- Return array of all available prompts +- Each prompt MUST have: + - `name` (string) + - `description` (string) + - `arguments` (array, optional) - list of required arguments + +### 4.2. Get Prompt + +**Endpoint**: `prompts/get` + +**Requirements**: +- Accept prompt name and arguments +- Return prompt messages +- Response MUST have `messages` array +- Each message MUST have `role` and `content` + +### 4.3. Required Prompts + +Implement these prompts with exact names: + +#### `test_simple_prompt` + +**Arguments**: None + +**Returns**: +```typescript +{ + messages: [ + { + role: "user", + content: { + type: "text", + text: "This is a simple prompt for testing." + } + } + ] +} +``` + +#### `test_prompt_with_arguments` + +**Arguments**: +- `arg1` (string, required) - First test argument +- `arg2` (string, required) - Second test argument + +**Metadata** (for `prompts/list`): +```typescript +{ + name: "test_prompt_with_arguments", + description: "A prompt with required arguments", + arguments: [ + { name: "arg1", description: "First test argument", required: true }, + { name: "arg2", description: "Second test argument", required: true } + ] +} +``` + +**Returns** (with args `{arg1: "hello", arg2: "world"}`): +```typescript +{ + messages: [ + { + role: "user", + content: { + type: "text", + text: "Prompt with arguments: arg1='hello', arg2='world'" + } + } + ] +} +``` + +#### `test_prompt_with_embedded_resource` + +**Arguments**: +- `resourceUri` (string, required) - URI of the resource to embed + +**Returns**: +```typescript +{ + messages: [ + { + role: "user", + content: { + type: "resource", + resource: { + uri: "", + mimeType: "text/plain", + text: "Embedded resource content for testing." + } + } + }, + { + role: "user", + content: { + type: "text", + text: "Please process the embedded resource above." + } + } + ] +} +``` + +#### `test_prompt_with_image` + +**Arguments**: None + +**Returns**: +```typescript +{ + messages: [ + { + role: "user", + content: { + type: "image", + data: "", + mimeType: "image/png" + } + }, + { + role: "user", + content: { + type: "text", + text: "Please analyze the image above." + } + } + ] +} +``` + +--- + +## 5. Logging Requirements + +### 5.1. Set Log Level + +**Endpoint**: `logging/setLevel` + +**Requirements**: +- Accept log level setting +- Filter subsequent log notifications based on level +- Return empty object `{}` + +**Log Levels** (in order of severity): +- `debug` +- `info` +- `notice` +- `warning` +- `error` +- `critical` +- `alert` +- `emergency` + +### 5.2. Log Notifications + +**Notification**: `notifications/message` + +**Requirements**: +- Send log notifications during operations +- Each log MUST have: + - `level` (string) - one of the log levels above + - `data` (any) - log message or structured data + - `logger` (string, optional) - logger name + +**Example**: +```typescript +{ + method: "notifications/message", + params: { + level: "info", + logger: "conformance-test-server", + data: "Tool execution started" + } +} +``` + +**Implementation Note**: When no client is connected, log notifications may fail. Handle this gracefully by catching promise rejections: + +```typescript +mcpServer.server.notification({ + method: "notifications/message", + params: { level, logger, data } +}).catch(() => { + // Ignore error if no client is connected +}); +``` + +--- + +## 6. Pagination (Utility Feature) + +### 6.1. Cursor-Based Pagination + +**Purpose**: Allow servers to return large result sets in manageable chunks. + +**Applies To**: +- `tools/list` +- `resources/list` +- `resources/templates/list` +- `prompts/list` + +**Requirements**: + +**Request Format**: +```typescript +{ + "method": "tools/list", // or resources/list, prompts/list, etc. + "params": { + "cursor": "optional-opaque-token" // Optional: for subsequent pages + } +} +``` + +**Response Format**: +```typescript +{ + "tools": [ /* array of tools */ ], // or resources, prompts, etc. + "nextCursor": "optional-opaque-token" // Optional: indicates more results available +} +``` + +**Implementation Requirements**: +- If `cursor` parameter is provided, return results starting after that position +- If more results are available, include `nextCursor` in response +- Cursor tokens MUST be opaque strings (format is server-defined) +- Page size is determined by server (clients MUST NOT assume fixed page size) +- For conformance testing, pagination is optional (all items can be returned in single response) + +**Example**: +```typescript +// First request (no cursor) +Request: { "method": "tools/list" } +Response: { + "tools": [/* first 10 tools */], + "nextCursor": "page2token" +} + +// Second request (with cursor) +Request: { "method": "tools/list", "params": { "cursor": "page2token" } } +Response: { + "tools": [/* next 10 tools */], + "nextCursor": "page3token" +} + +// Last page (no nextCursor) +Request: { "method": "tools/list", "params": { "cursor": "page3token" } } +Response: { + "tools": [/* remaining tools */] + // No nextCursor = end of results +} +``` + +--- + +## 7. Completion Requirements + +### 6.1. Complete Request + +**Endpoint**: `completion/complete` + +**Requirements**: +- Accept completion requests for prompt or resource template arguments +- Provide contextual suggestions based on partial input +- Return array of completion values ranked by relevance + +**Request Format**: +```typescript +{ + "method": "completion/complete", + "params": { + "ref": { + "type": "ref/prompt", // or "ref/resource" + "name": "test_prompt_with_arguments" // or uri for resources + }, + "argument": { + "name": "arg1", + "value": "par" // partial value to complete + } + } +} +``` + +**Response Format**: +```typescript +{ + "completion": { + "values": ["paris", "park", "party"], // Up to 100 items + "total": 150, // Optional: total available matches + "hasMore": true // Optional: indicates more results exist + } +} +``` + +**Implementation Note**: For conformance testing, completion support can be minimal or return empty arrays. The capability just needs to be declared and the endpoint must respond correctly. + +--- + +## 8. Testing Your Server + +### 8.1. Starting Your Server + +```bash +# Example for TypeScript +cd examples/servers/typescript +npm install +npm start +``` + +Server should output: +``` +MCP Conformance Test Server running on http://localhost:3000 + - MCP endpoint: http://localhost:3000/mcp +``` + +### 8.2. Running Conformance Tests + +```bash +# Single test +npm run test:server -- --server-url http://localhost:3000/mcp --scenario server-initialize + +# All tests +npm run test:server -- --server-url http://localhost:3000/mcp --all +``` + + From 988a8f8faf94756b730cc0839ae45a3c23e4a2c8 Mon Sep 17 00:00:00 2001 From: ihrpr Date: Sun, 2 Nov 2025 16:57:06 +0000 Subject: [PATCH 2/5] initial version of everything server and tests - tets are very simple --- README.md | 6 + examples/servers/typescript/README.md | 162 ++ .../servers/typescript/everything-server.ts | 736 ++++++++ examples/servers/typescript/package-lock.json | 1679 +++++++++++++++++ examples/servers/typescript/package.json | 27 + examples/servers/typescript/tsconfig.json | 10 + package-lock.json | 108 ++ package.json | 2 + src/runner.ts | 1 - src/scenarios/client/tools_call.ts | 5 +- src/scenarios/index.ts | 83 +- src/scenarios/server/client-helper.ts | 89 + src/scenarios/server/lifecycle.ts | 102 + src/scenarios/server/prompts.ts | 333 ++++ src/scenarios/server/resources.ts | 365 ++++ src/scenarios/server/tools.ts | 773 ++++++++ src/scenarios/server/utils.ts | 130 ++ src/server-runner.ts | 110 +- test-tool-call.ts | 39 + 19 files changed, 4730 insertions(+), 30 deletions(-) create mode 100644 examples/servers/typescript/README.md create mode 100644 examples/servers/typescript/everything-server.ts create mode 100644 examples/servers/typescript/package-lock.json create mode 100644 examples/servers/typescript/package.json create mode 100644 examples/servers/typescript/tsconfig.json create mode 100644 src/scenarios/server/client-helper.ts create mode 100644 src/scenarios/server/lifecycle.ts create mode 100644 src/scenarios/server/prompts.ts create mode 100644 src/scenarios/server/resources.ts create mode 100644 src/scenarios/server/tools.ts create mode 100644 src/scenarios/server/utils.ts create mode 100644 test-tool-call.ts diff --git a/README.md b/README.md index 83bf5f1..15e2e94 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,17 @@ A framework for testing MCP (Model Context Protocol) client implementations agai ## Quick Start +### Testing Clients ```bash npm install npm run start -- --command "tsx examples/clients/typescript/test1.ts" --scenario initialize ``` +### Testing Servers +```bash +npm run test:server -- --server-url http://localhost:3000/mcp --all +``` + ## Overview The conformance test framework validates MCP client implementations by: diff --git a/examples/servers/typescript/README.md b/examples/servers/typescript/README.md new file mode 100644 index 0000000..1bab7e0 --- /dev/null +++ b/examples/servers/typescript/README.md @@ -0,0 +1,162 @@ +# MCP Conformance Test Server + +A reference implementation of an MCP server that implements all features required for conformance testing. + +## Features + +This server implements: + +### Tools +- `test_simple_text` - Returns simple text content +- `test_image_content` - Returns image content (base64 PNG) +- `test_audio_content` - Returns audio content (base64 WAV) +- `test_embedded_resource` - Returns embedded resource +- `test_multiple_content_types` - Returns mixed content types +- `test_tool_with_logging` - Emits log messages during execution +- `test_tool_with_progress` - Reports progress notifications +- `test_error_handling` - Returns error response +- `test_sampling` - Requests LLM completion from client +- `test_elicitation` - Requests user input from client +- `test_dynamic_tool` - Dynamically added/removed tool + +### Resources +- `test://static-text` - Static text resource +- `test://static-binary` - Static binary resource (image) +- `test://template/{id}/data` - Resource template with parameter +- `test://watched-resource` - Subscribable resource with updates + +### Prompts +- `test_simple_prompt` - Simple prompt without arguments +- `test_prompt_with_arguments(arg1, arg2)` - Parameterized prompt +- `test_prompt_with_embedded_resource(resourceUri)` - Prompt with embedded resource +- `test_prompt_with_image` - Prompt with image content + +### Other Capabilities +- Logging at all levels (debug, info, notice, warning, error, critical, alert, emergency) +- Completion support for prompt and resource arguments +- List changed notifications for tools, resources, and prompts +- Resource subscription and update notifications + +## Installation + +```bash +npm install +``` + +## Running the Server + +```bash +npm start +``` + +The server will start on `http://localhost:3000` (or the port specified in `PORT` environment variable). + +## Endpoints + +### MCP Endpoint +- `POST /mcp` - Main MCP protocol endpoint + +### Health Check +- `GET /health` - Server health check + +## Automatic Behaviors + +The server automatically demonstrates dynamic capabilities: + +- **Dynamic Tool** - `test_dynamic_tool` is automatically added 2 seconds after server starts +- **Dynamic Resource** - `test://dynamic-resource` is automatically added 2 seconds after server starts +- **Dynamic Prompt** - `test_dynamic_prompt` is automatically added 2 seconds after server starts +- **Resource Updates** - `test://watched-resource` automatically updates every 3 seconds with new content + +These behaviors allow testing of MCP notifications without requiring manual triggers. + +## Example Usage + +### Starting the Server +```bash +npm start +``` + +### Testing with MCP Inspector +```bash +npx @modelcontextprotocol/inspector http://localhost:3000/mcp +``` + +### Testing with curl + +#### Initialize +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-06-18", + "capabilities": {}, + "clientInfo": { + "name": "test-client", + "version": "1.0.0" + } + } + }' +``` + +#### List Tools +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list" + }' +``` + +#### Call Tool +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "test_simple_text", + "arguments": {} + } + }' +``` + +## Conformance Testing + +This server implements the MCP Server Conformance Requirements specified in `../../../SERVER_REQUIREMENTS.md`. All tools, resources, and prompts use standardized naming conventions for consistent testing across SDK implementations. + +To run conformance tests against this server: + +```bash +cd ../../../ +npm run test:server -- --server-url http://localhost:3000/mcp --all +``` + +## Implementation Notes + +- All tool, resource, and prompt names follow the standardized naming conventions (`test_*` for tools/prompts, `test://` for resources) +- Names are descriptive of the feature being tested (e.g., `test_image_content`, `test_tool_with_progress`) +- The server uses the TypeScript MCP SDK (`@modelcontextprotocol/sdk`) high-level API +- Uses `registerTool()`, `registerResource()`, and `registerPrompt()` methods +- Transport is Streamable HTTP (Express) for web-based testing compatibility +- Promise rejections from notifications are caught and handled gracefully + +## For SDK Implementers + +If you're implementing MCP in another language/SDK: + +1. **Read the Requirements**: See `../../../SERVER_REQUIREMENTS.md` for complete specifications +2. **Use This as Reference**: This TypeScript implementation demonstrates all required features +3. **Follow Naming Conventions**: Use exact tool/resource/prompt names specified in requirements +4. **Implement Automatic Behaviors**: Dynamic registration after 2s, resource updates every 3s +5. **Handle Notifications Carefully**: Catch/ignore errors when no client is connected + +**Goal**: All SDK example servers provide the same interface, enabling a single test suite to verify conformance across all implementations. diff --git a/examples/servers/typescript/everything-server.ts b/examples/servers/typescript/everything-server.ts new file mode 100644 index 0000000..fcac586 --- /dev/null +++ b/examples/servers/typescript/everything-server.ts @@ -0,0 +1,736 @@ +#!/usr/bin/env node + +/** + * MCP Everything Server - Conformance Test Server + * + * Server implementing all MCP features for conformance testing based on Conformnace Server Specification. + * Should be using registerTool(), registerResource(), and registerPrompt(). + * we use tool() instead of registerTool() as there is a bug with logging in registerTool(). + */ + +import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { z } from 'zod'; +import express from 'express'; +import cors from 'cors'; +import { randomUUID } from 'crypto'; + +// Server state +const resourceSubscriptions = new Set(); +const watchedResourceContent = 'Watched resource content'; + +// Session management +const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; +const servers: { [sessionId: string]: McpServer } = {}; + +// Sample base64 encoded 1x1 red PNG pixel for testing +const TEST_IMAGE_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=='; + +// Sample base64 encoded minimal WAV file for testing +const TEST_AUDIO_BASE64 = 'UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA='; + +// Function to create a new MCP server instance (one per session) +function createMcpServer() { + const mcpServer = new McpServer( + { + name: 'mcp-conformance-test-server', + version: '1.0.0' + }, + { + capabilities: { + tools: { + listChanged: true + }, + resources: { + subscribe: true, + listChanged: true + }, + prompts: { + listChanged: true + }, + logging: {}, + completions: {} + } + } + ); + + // Helper to send log messages using the underlying server + function sendLog(level: 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency', message: string, data?: any) { + mcpServer.server.notification({ + method: 'notifications/message', + params: { + level, + logger: 'conformance-test-server', + data: data || message + } + }).catch(() => { + // Ignore error if no client is connected + }); + } + + // ===== TOOLS ===== + + // Simple text tool + mcpServer.tool( + 'test_simple_text', + 'Tests simple text content response', + {}, + async () => { + return { + content: [{ type: 'text', text: 'This is a simple text response for testing.' }] + }; + } + ); + + // Image content tool + mcpServer.registerTool( + 'test_image_content', + { + description: 'Tests image content response' + }, + async () => { + return { + content: [{ type: 'image', data: TEST_IMAGE_BASE64, mimeType: 'image/png' }] + }; + } + ); + + // Audio content tool + mcpServer.registerTool( + 'test_audio_content', + { + description: 'Tests audio content response' + }, + async () => { + return { + content: [{ type: 'audio', data: TEST_AUDIO_BASE64, mimeType: 'audio/wav' }] + }; + } + ); + + // Embedded resource tool + mcpServer.registerTool( + 'test_embedded_resource', + { + description: 'Tests embedded resource content response' + }, + async () => { + return { + content: [{ + type: 'resource', + resource: { + uri: 'test://embedded-resource', + mimeType: 'text/plain', + text: 'This is an embedded resource content.' + } + }] + }; + } + ); + + // Multiple content types tool + mcpServer.registerTool( + 'test_multiple_content_types', + { + description: 'Tests response with multiple content types (text, image, resource)' + }, + async () => { + return { + content: [ + { type: 'text', text: 'Multiple content types test:' }, + { type: 'image', data: TEST_IMAGE_BASE64, mimeType: 'image/png' }, + { + type: 'resource', + resource: { + uri: 'test://mixed-content-resource', + mimeType: 'application/json', + text: JSON.stringify({ test: 'data', value: 123 }) + } + } + ] + }; + } + ); + + // Tool with logging - registerTool with empty inputSchema to get (args, extra) signature + mcpServer.registerTool( + 'test_tool_with_logging', + { + description: 'Tests tool that emits log messages during execution', + inputSchema: {} // Empty schema so callback gets (args, extra) instead of just (extra) + }, + async (_args, { sendNotification }) => { + await sendNotification({ + method: 'notifications/message', + params: { + level: 'info', + data: 'Tool execution started' + } + }); + await new Promise(resolve => setTimeout(resolve, 50)); + + await sendNotification({ + method: 'notifications/message', + params: { + level: 'info', + data: 'Tool processing data' + } + }); + await new Promise(resolve => setTimeout(resolve, 50)); + + await sendNotification({ + method: 'notifications/message', + params: { + level: 'info', + data: 'Tool execution completed' + } + }); + return { + content: [{ type: 'text', text: 'Tool with logging executed successfully' }] + }; + } + ); + + + // Tool with progress - registerTool with empty inputSchema to get (args, extra) signature + mcpServer.registerTool( + 'test_tool_with_progress', + { + description: 'Tests tool that reports progress notifications', + inputSchema: {} // Empty schema so callback gets (args, extra) instead of just (extra) + }, + async (_args, { sendNotification, _meta }) => { + const progressToken = _meta?.progressToken ?? 0; + console.log("???? Progress token:", progressToken); + await sendNotification({ + method: 'notifications/progress', + params: { + progressToken, + progress: 0, + total: 100, + message: `Completed step ${0} of ${100}` + } + }); + await new Promise(resolve => setTimeout(resolve, 50)); + + await sendNotification({ + method: 'notifications/progress', + params: { + progressToken, + progress: 50, + total: 100, + message: `Completed step ${50} of ${100}` + } + }); + await new Promise(resolve => setTimeout(resolve, 50)); + + await sendNotification({ + method: 'notifications/progress', + params: { + progressToken, + progress: 100, + total: 100, + message: `Completed step ${100} of ${100}` + } + }); + + return { + content: [{ type: 'text', text: String(progressToken) }] + }; + } + ); + + // Error handling tool + mcpServer.registerTool( + 'test_error_handling', + { + description: 'Tests error response handling' + }, + async () => { + throw new Error('This tool intentionally returns an error for testing'); + } + ); + + // Sampling tool - requests LLM completion from client + mcpServer.registerTool( + 'test_sampling', + { + description: 'Tests server-initiated sampling (LLM completion request)', + inputSchema: { + prompt: z.string().describe('The prompt to send to the LLM') + } + }, + async (args: { prompt: string }) => { + try { + // Request sampling from client + const result = await mcpServer.server.request( + { + method: 'sampling/createMessage', + params: { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: args.prompt + } + } + ], + maxTokens: 100 + } + }, + (z.object({ method: z.literal('sampling/createMessage') }).passthrough() as any) + ); + + const samplingResult = result as any; + const modelResponse = samplingResult.content?.text || samplingResult.message?.content?.text || 'No response'; + + return { + content: [{ + type: 'text', + text: `LLM response: ${modelResponse}` + }] + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: `Sampling not supported or error: ${error.message}` + }] + }; + } + } + ); + + // Elicitation tool - requests user input from client + mcpServer.registerTool( + 'test_elicitation', + { + description: 'Tests server-initiated elicitation (user input request)', + inputSchema: { + message: z.string().describe('The message to show the user') + } + }, + async (args: { message: string }) => { + try { + // Request user input from client + const result = await mcpServer.server.request( + { + method: 'elicitation/create', + params: { + message: args.message, + requestedSchema: { + type: 'object', + properties: { + response: { + type: 'string', + description: 'User\'s response' + } + }, + required: ['response'] + } + } + }, + (z.object({ method: z.literal('elicitation/create') }).passthrough() as any) + ); + + const elicitResult = result as any; + return { + content: [{ + type: 'text', + text: `User response: action=${elicitResult.action}, content=${JSON.stringify(elicitResult.content || {})}` + }] + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: `Elicitation not supported or error: ${error.message}` + }] + }; + } + } + ); + + // Dynamic tool (registered later via timer) + + // ===== RESOURCES ===== + + // Static text resource + mcpServer.registerResource( + 'static-text', + 'test://static-text', + { + title: 'Static Text Resource', + description: 'A static text resource for testing', + mimeType: 'text/plain' + }, + async () => { + return { + contents: [{ + uri: 'test://static-text', + mimeType: 'text/plain', + text: 'This is the content of the static text resource.' + }] + }; + } + ); + + // Static binary resource + mcpServer.registerResource( + 'static-binary', + 'test://static-binary', + { + title: 'Static Binary Resource', + description: 'A static binary resource (image) for testing', + mimeType: 'image/png' + }, + async () => { + return { + contents: [{ + uri: 'test://static-binary', + mimeType: 'image/png', + blob: TEST_IMAGE_BASE64 + }] + }; + } + ); + + // Resource template + mcpServer.registerResource( + 'template', + new ResourceTemplate('test://template/{id}/data', { + list: undefined + }), + { + title: 'Resource Template', + description: 'A resource template with parameter substitution', + mimeType: 'application/json' + }, + async (uri, variables) => { + const id = variables.id; + return { + contents: [{ + uri: uri.toString(), + mimeType: 'application/json', + text: JSON.stringify({ + id, + templateTest: true, + data: `Data for ID: ${id}` + }) + }] + }; + } + ); + + // Watched resource + mcpServer.registerResource( + 'watched-resource', + 'test://watched-resource', + { + title: 'Watched Resource', + description: 'A resource that auto-updates every 3 seconds', + mimeType: 'text/plain' + }, + async () => { + return { + contents: [{ + uri: 'test://watched-resource', + mimeType: 'text/plain', + text: watchedResourceContent + }] + }; + } + ); + + // Subscribe/Unsubscribe handlers + mcpServer.server.setRequestHandler( + z.object({ method: z.literal('resources/subscribe') }).passthrough(), + async (request: any) => { + const uri = request.params.uri; + resourceSubscriptions.add(uri); + sendLog('info', `Subscribed to resource: ${uri}`); + return {}; + } + ); + + mcpServer.server.setRequestHandler( + z.object({ method: z.literal('resources/unsubscribe') }).passthrough(), + async (request: any) => { + const uri = request.params.uri; + resourceSubscriptions.delete(uri); + sendLog('info', `Unsubscribed from resource: ${uri}`); + return {}; + } + ); + + // ===== PROMPTS ===== + + // Simple prompt + mcpServer.registerPrompt( + 'test_simple_prompt', + { + title: 'Simple Test Prompt', + description: 'A simple prompt without arguments' + }, + async () => { + return { + messages: [{ + role: 'user', + content: { type: 'text', text: 'This is a simple prompt for testing.' } + }] + }; + } + ); + + // Prompt with arguments + mcpServer.registerPrompt( + 'test_prompt_with_arguments', + { + title: 'Prompt With Arguments', + description: 'A prompt with required arguments', + argsSchema: { + arg1: z.string().describe('First test argument'), + arg2: z.string().describe('Second test argument') + } + }, + async (args) => { + const { arg1, arg2 } = args; + return { + messages: [{ + role: 'user', + content: { + type: 'text', + text: `Prompt with arguments: arg1='${arg1}', arg2='${arg2}'` + } + }] + }; + } + ); + + // Prompt with embedded resource + mcpServer.registerPrompt( + 'test_prompt_with_embedded_resource', + { + title: 'Prompt With Embedded Resource', + description: 'A prompt that includes an embedded resource', + argsSchema: { + resourceUri: z.string().describe('URI of the resource to embed') + } + }, + async (args) => { + const uri = args.resourceUri; + return { + messages: [ + { + role: 'user', + content: { + type: 'resource', + resource: { + uri, + mimeType: 'text/plain', + text: 'Embedded resource content for testing.' + } + } + }, + { + role: 'user', + content: { type: 'text', text: 'Please process the embedded resource above.' } + } + ] + }; + } + ); + + // Prompt with image + mcpServer.registerPrompt( + 'test_prompt_with_image', + { + title: 'Prompt With Image', + description: 'A prompt that includes image content' + }, + async () => { + return { + messages: [ + { + role: 'user', + content: { type: 'image', data: TEST_IMAGE_BASE64, mimeType: 'image/png' } + }, + { + role: 'user', + content: { type: 'text', text: 'Please analyze the image above.' } + } + ] + }; + } + ); + + // ===== LOGGING ===== + + mcpServer.server.setRequestHandler( + z.object({ method: z.literal('logging/setLevel') }).passthrough(), + async (request: any) => { + const level = request.params.level; + sendLog('info', `Log level set to: ${level}`); + return {}; + } + ); + + // ===== COMPLETION ===== + + mcpServer.server.setRequestHandler( + z.object({ method: z.literal('completion/complete') }).passthrough(), + async (_request: any) => { + // Basic completion support - returns empty array for conformance + // Real implementations would provide contextual suggestions + return { + completion: { + values: [], + total: 0, + hasMore: false + } + }; + } + ); + + return mcpServer; +} + +// Helper to check if request is an initialize request +function isInitializeRequest(body: any): boolean { + return body?.method === 'initialize'; +} + +// ===== EXPRESS APP ===== + +const app = express(); +app.use(express.json()); + +// Configure CORS to expose Mcp-Session-Id header for browser-based clients +app.use(cors({ + origin: '*', // Allow all origins + exposedHeaders: ['Mcp-Session-Id'], + allowedHeaders: ['Content-Type', 'mcp-session-id', 'last-event-id'] +})); + +// Handle POST requests - stateful mode +app.post('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + + try { + let transport: StreamableHTTPServerTransport; + + if (sessionId && transports[sessionId]) { + // Reuse existing transport for established sessions + transport = transports[sessionId]; + } else if (!sessionId && isInitializeRequest(req.body)) { + // Create new transport for initialization requests + const mcpServer = createMcpServer(); + + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (newSessionId) => { + transports[newSessionId] = transport; + servers[newSessionId] = mcpServer; + console.log(`Session initialized with ID: ${newSessionId}`); + } + }); + + transport.onclose = () => { + const sid = transport.sessionId; + if (sid && transports[sid]) { + delete transports[sid]; + if (servers[sid]) { + servers[sid].close(); + delete servers[sid]; + } + console.log(`Session ${sid} closed`); + } + }; + + await mcpServer.connect(transport); + await transport.handleRequest(req, res, req.body); + return; + } else { + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Invalid or missing session ID' + }, + id: null + }); + return; + } + + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error('Error handling MCP request:', error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error' + }, + id: null + }); + } + } +}); + +// Handle GET requests - SSE streams for sessions +app.get('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + const lastEventId = req.headers['last-event-id'] as string | undefined; + if (lastEventId) { + console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`); + } else { + console.log(`Establishing SSE stream for session ${sessionId}`); + } + + try { + const transport = transports[sessionId]; + await transport.handleRequest(req, res); + } catch (error) { + console.error('Error handling SSE stream:', error); + if (!res.headersSent) { + res.status(500).send('Error establishing SSE stream'); + } + } +}); + +// Handle DELETE requests - session termination +app.delete('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + console.log(`Received session termination request for session ${sessionId}`); + + try { + const transport = transports[sessionId]; + await transport.handleRequest(req, res); + } catch (error) { + console.error('Error handling termination:', error); + if (!res.headersSent) { + res.status(500).send('Error processing session termination'); + } + } +}); + +// Start server +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`MCP Conformance Test Server running on http://localhost:${PORT}`); + console.log(` - MCP endpoint: http://localhost:${PORT}/mcp`); +}); diff --git a/examples/servers/typescript/package-lock.json b/examples/servers/typescript/package-lock.json new file mode 100644 index 0000000..14b3c85 --- /dev/null +++ b/examples/servers/typescript/package-lock.json @@ -0,0 +1,1679 @@ +{ + "name": "mcp-conformance-test-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-conformance-test-server", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.20.1", + "@types/cors": "^2.8.19", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "tsx": "^4.7.0", + "typescript": "^5.5.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.20.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@modelcontextprotocol/sdk/-/sdk-1.20.1.tgz", + "integrity": "sha512-j/P+yuxXfgxb+mW7OEoRCM3G47zCTDqUPivJo/VzpjbG8I9csTXtOprCf5FfOfHK4whOJny0aHuBEON+kS7CCA==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.8.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/node/-/node-24.8.0.tgz", + "integrity": "sha512-5x08bUtU8hfboMTrJ7mEO4CpepS9yBwAqcL52y86SWNmbPX8LVbNs3EP4cNrIZgdjk2NAlP2ahNihozpoZIxSg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.14.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-generator-function": { + "version": "1.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/async-generator-function/-/async-generator-function-1.0.0.tgz", + "integrity": "sha512-+NAXNqgCrB95ya4Sr66i1CL2hqLVckAk7xwRYWdcm39/ELQ6YNn1aw5r0bdQtqNZgQpEWzc5yc/igXc7aL5SLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/get-intrinsic/-/get-intrinsic-1.3.1.tgz", + "integrity": "sha512-fk1ZVEeOX9hVZ6QzoBNEC55+Ucqg4sTVwrVuigZhuRPESVFpMyXnd3sbXvPOwp7Y9riVyANiqhEuRF0G1aVSeQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.12.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/get-tsconfig/-/get-tsconfig-4.12.0.tgz", + "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.14.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/wrappy/1.0.2/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/examples/servers/typescript/package.json b/examples/servers/typescript/package.json new file mode 100644 index 0000000..592d455 --- /dev/null +++ b/examples/servers/typescript/package.json @@ -0,0 +1,27 @@ +{ + "name": "mcp-conformance-test-server", + "version": "1.0.0", + "description": "Reference implementation of MCP server for conformance testing", + "type": "module", + "main": "everything-server.ts", + "scripts": { + "start": "tsx everything-server.ts", + "dev": "tsx watch everything-server.ts" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "conformance", + "testing" + ], + "dependencies": { + "@modelcontextprotocol/sdk": "^1.20.1", + "@types/cors": "^2.8.19", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "tsx": "^4.7.0", + "typescript": "^5.5.4" + } +} diff --git a/examples/servers/typescript/tsconfig.json b/examples/servers/typescript/tsconfig.json new file mode 100644 index 0000000..89fe38c --- /dev/null +++ b/examples/servers/typescript/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "*.ts" + ] +} diff --git a/package-lock.json b/package-lock.json index 8fec0b0..5336baf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "devDependencies": { "@eslint/js": "^9.8.0", "@jest/globals": "^29.7.0", + "@types/express": "^5.0.3", "@types/jest": "^29.5.8", "eslint": "^9.8.0", "eslint-config-prettier": "^10.1.8", @@ -1787,6 +1788,27 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1794,6 +1816,31 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1804,6 +1851,13 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1849,6 +1903,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/mime/1.3.5/mime-1.3.5.tgz", + "integrity": "sha1-HvMC4Bz30rWg+lJnkMkSO/HQZpA=", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.10.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", @@ -1859,6 +1920,53 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.0", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/send/-/send-1.2.0.tgz", + "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.9", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/serve-static/-/serve-static-1.15.9.tgz", + "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", diff --git a/package.json b/package.json index 21f0938..5e28b4e 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "type": "module", "scripts": { "start": "tsx src/runner.ts", + "test:server": "tsx src/server-runner.ts", "test": "jest", "lint": "eslint src/ && prettier --check .", "lint:fix": "eslint src/ --fix && prettier --write .", @@ -12,6 +13,7 @@ "devDependencies": { "@eslint/js": "^9.8.0", "@jest/globals": "^29.7.0", + "@types/express": "^5.0.3", "@types/jest": "^29.5.8", "eslint": "^9.8.0", "eslint-config-prettier": "^10.1.8", diff --git a/src/runner.ts b/src/runner.ts index 0ee592e..ff1d929 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -1,4 +1,3 @@ import { runConformanceTest } from './runner/index.js'; export { runConformanceTest }; -export type { ScenarioConfig } from './runner/index.js'; diff --git a/src/scenarios/client/tools_call.ts b/src/scenarios/client/tools_call.ts index 606ce88..4c45af4 100644 --- a/src/scenarios/client/tools_call.ts +++ b/src/scenarios/client/tools_call.ts @@ -105,8 +105,8 @@ function createServer(checks: ConformanceCheck[]): express.Application { status: 'INFO', timestamp: new Date().toISOString(), details: { - // TODO: this isn't working - body: JSON.stringify(res.body) + // TODO: Response body not available in express middleware + statusCode: res.statusCode } }); }); @@ -125,6 +125,7 @@ function createServer(checks: ConformanceCheck[]): express.Application { export class ToolsCallScenario implements Scenario { name = 'tools_call'; + description = 'Tests calling tools with various parameter types'; private app: express.Application | null = null; private httpServer: any = null; private checks: ConformanceCheck[] = []; diff --git a/src/scenarios/index.ts b/src/scenarios/index.ts index 0d9baec..7bdd9e6 100644 --- a/src/scenarios/index.ts +++ b/src/scenarios/index.ts @@ -3,12 +3,93 @@ import { InitializeScenario } from './client/initialize.js'; import { ToolsCallScenario } from './client/tools_call.js'; import { ServerInitializeClientScenario } from './server/server_initialize.js'; +// Import all new server test scenarios +import { + ServerInitializeScenario, + ServerInitializeCapabilitiesScenario +} from './server/lifecycle.js'; + +import { + LoggingSetLevelScenario, + CompletionCompleteScenario +} from './server/utils.js'; + +import { + ToolsListScenario, + ToolsCallSimpleTextScenario, + ToolsCallImageScenario, + ToolsCallMultipleContentTypesScenario, + ToolsCallWithLoggingScenario, + ToolsCallErrorScenario, + ToolsCallWithProgressScenario, + ToolsCallSamplingScenario, + ToolsCallElicitationScenario, + ToolsCallAudioScenario, + ToolsCallEmbeddedResourceScenario +} from './server/tools.js'; + +import { + ResourcesListScenario, + ResourcesReadTextScenario, + ResourcesReadBinaryScenario, + ResourcesTemplateReadScenario, + ResourcesSubscribeScenario, + ResourcesUnsubscribeScenario +} from './server/resources.js'; + +import { + PromptsListScenario, + PromptsGetSimpleScenario, + PromptsGetWithArgsScenario, + PromptsGetEmbeddedResourceScenario, + PromptsGetWithImageScenario +} from './server/prompts.js'; + export const scenarios = new Map([ ['initialize', new InitializeScenario()], ['tools-call', new ToolsCallScenario()] ]); -export const clientScenarios = new Map([['initialize', new ServerInitializeClientScenario()]]); +export const clientScenarios = new Map([ + // Legacy + ['initialize', new ServerInitializeClientScenario()], + + // Lifecycle scenarios + ['server-initialize', new ServerInitializeScenario()], + ['server-initialize-capabilities', new ServerInitializeCapabilitiesScenario()], + + // Utilities scenarios + ['logging-set-level', new LoggingSetLevelScenario()], + ['completion-complete', new CompletionCompleteScenario()], + + // Tools scenarios + ['tools-list', new ToolsListScenario()], + ['tools-call-simple-text', new ToolsCallSimpleTextScenario()], + ['tools-call-image', new ToolsCallImageScenario()], + ['tools-call-audio', new ToolsCallAudioScenario()], + ['tools-call-embedded-resource', new ToolsCallEmbeddedResourceScenario()], + ['tools-call-mixed-content', new ToolsCallMultipleContentTypesScenario()], + ['tools-call-with-logging', new ToolsCallWithLoggingScenario()], + ['tools-call-error', new ToolsCallErrorScenario()], + ['tools-call-with-progress', new ToolsCallWithProgressScenario()], + ['tools-call-sampling', new ToolsCallSamplingScenario()], + ['tools-call-elicitation', new ToolsCallElicitationScenario()], + + // Resources scenarios + ['resources-list', new ResourcesListScenario()], + ['resources-read-text', new ResourcesReadTextScenario()], + ['resources-read-binary', new ResourcesReadBinaryScenario()], + ['resources-templates-read', new ResourcesTemplateReadScenario()], + ['resources-subscribe', new ResourcesSubscribeScenario()], + ['resources-unsubscribe', new ResourcesUnsubscribeScenario()], + + // Prompts scenarios + ['prompts-list', new PromptsListScenario()], + ['prompts-get-simple', new PromptsGetSimpleScenario()], + ['prompts-get-with-args', new PromptsGetWithArgsScenario()], + ['prompts-get-embedded-resource', new PromptsGetEmbeddedResourceScenario()], + ['prompts-get-with-image', new PromptsGetWithImageScenario()] +]); export function registerScenario(name: string, scenario: Scenario): void { scenarios.set(name, scenario); diff --git a/src/scenarios/server/client-helper.ts b/src/scenarios/server/client-helper.ts new file mode 100644 index 0000000..08f023a --- /dev/null +++ b/src/scenarios/server/client-helper.ts @@ -0,0 +1,89 @@ +/** + * Helper utilities for creating MCP clients to test servers + */ + +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { LoggingMessageNotificationSchema, ProgressNotificationSchema } from '@modelcontextprotocol/sdk/types.js'; + +export interface MCPClientConnection { + client: Client; + close: () => Promise; +} + +/** + * Create and connect an MCP client to a server + */ +export async function connectToServer(serverUrl: string): Promise { + const client = new Client( + { + name: 'conformance-test-client', + version: '1.0.0' + }, + { + capabilities: { + // Client capabilities + sampling: {}, + elicitation: {} + } + } + ); + + const transport = new StreamableHTTPClientTransport(new URL(serverUrl)); + + await client.connect(transport); + + return { + client, + close: async () => { + await client.close(); + } + }; +} + +/** + * Helper to collect notifications (logging and progress) + */ +export class NotificationCollector { + private loggingNotifications: any[] = []; + private progressNotifications: any[] = []; + + constructor(client: Client) { + // Set up notification handler for logging messages + client.setNotificationHandler( + LoggingMessageNotificationSchema, + (notification) => { + this.loggingNotifications.push(notification); + } + ); + + // Set up notification handler for progress notifications + client.setNotificationHandler( + ProgressNotificationSchema, + (notification) => { + this.progressNotifications.push(notification); + } + ); + } + + /** + * Get all collected logging notifications + */ + getLoggingNotifications(): any[] { + return this.loggingNotifications; + } + + /** + * Get all collected progress notifications + */ + getProgressNotifications(): any[] { + return this.progressNotifications; + } + + /** + * Get all notifications (for backward compatibility) + */ + getNotifications(): any[] { + return this.loggingNotifications; + } +} diff --git a/src/scenarios/server/lifecycle.ts b/src/scenarios/server/lifecycle.ts new file mode 100644 index 0000000..349b544 --- /dev/null +++ b/src/scenarios/server/lifecycle.ts @@ -0,0 +1,102 @@ +/** + * Lifecycle test scenarios for MCP servers + */ + +import { ClientScenario, ConformanceCheck } from '../../types.js'; +import { connectToServer } from './client-helper.js'; + +export class ServerInitializeScenario implements ClientScenario { + name = 'server-initialize'; + description = 'Test basic server initialization handshake'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + // The connection process already does initialization + // Check that we have a connected client + checks.push({ + id: 'server-initialize', + name: 'ServerInitialize', + description: 'Server responds to initialize request with valid structure', + status: 'SUCCESS', + timestamp: new Date().toISOString(), + specReferences: [{ + id: 'MCP-Initialize', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization' + }], + details: { + serverUrl, + connected: true + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'server-initialize', + name: 'ServerInitialize', + description: 'Server responds to initialize request with valid structure', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed to initialize: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Initialize', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization' + }] + }); + } + + return checks; + } +} + +export class ServerInitializeCapabilitiesScenario implements ClientScenario { + name = 'server-initialize-capabilities'; + description = 'Test server advertising all its capabilities'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + // Get server capabilities from the connection + // Note: The SDK should expose this, for now we'll just verify connection works + checks.push({ + id: 'server-initialize-capabilities', + name: 'ServerInitializeCapabilities', + description: 'Server declares its capabilities correctly', + status: 'SUCCESS', + timestamp: new Date().toISOString(), + specReferences: [{ + id: 'MCP-Capabilities', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/architecture/index#capability-negotiation' + }], + details: { + serverUrl, + note: 'Capabilities verified through successful connection' + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'server-initialize-capabilities', + name: 'ServerInitializeCapabilities', + description: 'Server declares its capabilities correctly', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Capabilities', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/architecture/index#capability-negotiation' + }] + }); + } + + return checks; + } +} diff --git a/src/scenarios/server/prompts.ts b/src/scenarios/server/prompts.ts new file mode 100644 index 0000000..ce4eace --- /dev/null +++ b/src/scenarios/server/prompts.ts @@ -0,0 +1,333 @@ +/** + * Prompts test scenarios for MCP servers + */ + +import { ClientScenario, ConformanceCheck } from '../../types.js'; +import { connectToServer } from './client-helper.js'; + +export class PromptsListScenario implements ClientScenario { + name = 'prompts-list'; + description = 'Test listing available prompts'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.listPrompts(); + + // Validate response structure + const errors: string[] = []; + if (!result.prompts) { + errors.push('Missing prompts array'); + } else { + if (!Array.isArray(result.prompts)) { + errors.push('prompts is not an array'); + } + + result.prompts.forEach((prompt, index) => { + if (!prompt.name) errors.push(`Prompt ${index}: missing name`); + if (!prompt.description) errors.push(`Prompt ${index}: missing description`); + }); + } + + checks.push({ + id: 'prompts-list', + name: 'PromptsList', + description: 'Server lists available prompts with valid structure', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Prompts-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts' + }], + details: { + promptCount: result.prompts?.length || 0, + prompts: result.prompts?.map(p => p.name) + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'prompts-list', + name: 'PromptsList', + description: 'Server lists available prompts with valid structure', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Prompts-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts' + }] + }); + } + + return checks; + } +} + +export class PromptsGetSimpleScenario implements ClientScenario { + name = 'prompts-get-simple'; + description = 'Test getting a simple prompt without arguments'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.getPrompt({ + name: 'test_simple_prompt' + }); + + // Validate response + const errors: string[] = []; + if (!result.messages) errors.push('Missing messages array'); + if (!Array.isArray(result.messages)) errors.push('messages is not an array'); + if (result.messages.length === 0) errors.push('messages array is empty'); + + result.messages.forEach((message: any, index: number) => { + if (!message.role) errors.push(`Message ${index}: missing role`); + if (!message.content) errors.push(`Message ${index}: missing content`); + }); + + checks.push({ + id: 'prompts-get-simple', + name: 'PromptsGetSimple', + description: 'Get simple prompt successfully', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + }], + details: { + messageCount: result.messages?.length || 0 + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'prompts-get-simple', + name: 'PromptsGetSimple', + description: 'Get simple prompt successfully', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + }] + }); + } + + return checks; + } +} + +export class PromptsGetWithArgsScenario implements ClientScenario { + name = 'prompts-get-with-args'; + description = 'Test parameterized prompt'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.getPrompt({ + name: 'test_prompt_with_arguments', + arguments: { + arg1: 'testValue1', + arg2: 'testValue2' + } + }); + + // Validate response + const errors: string[] = []; + if (!result.messages) errors.push('Missing messages array'); + if (result.messages.length === 0) errors.push('messages array is empty'); + + // Check if arguments were substituted + const messageText = JSON.stringify(result.messages); + if (!messageText.includes('testValue1')) { + errors.push('arg1 not substituted in prompt'); + } + if (!messageText.includes('testValue2')) { + errors.push('arg2 not substituted in prompt'); + } + + checks.push({ + id: 'prompts-get-with-args', + name: 'PromptsGetWithArgs', + description: 'Get parameterized prompt with argument substitution', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + }], + details: { + messageCount: result.messages?.length || 0, + messages: result.messages + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'prompts-get-with-args', + name: 'PromptsGetWithArgs', + description: 'Get parameterized prompt with argument substitution', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + }] + }); + } + + return checks; + } +} + +export class PromptsGetEmbeddedResourceScenario implements ClientScenario { + name = 'prompts-get-embedded-resource'; + description = 'Test prompt with embedded resource content'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.getPrompt({ + name: 'test_prompt_with_embedded_resource', + arguments: { + resourceUri: 'test://example-resource' + } + }); + + // Validate response + const errors: string[] = []; + if (!result.messages) errors.push('Missing messages array'); + + // Look for resource content + const hasResourceContent = result.messages.some((msg: any) => + msg.content?.type === 'resource' || + (msg.content?.resource !== undefined) + ); + + if (!hasResourceContent) { + errors.push('No embedded resource found in prompt'); + } + + checks.push({ + id: 'prompts-get-embedded-resource', + name: 'PromptsGetEmbeddedResource', + description: 'Get prompt with embedded resource', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Prompts-Embedded-Resources', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources' + }], + details: { + messageCount: result.messages?.length || 0, + messages: result.messages + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'prompts-get-embedded-resource', + name: 'PromptsGetEmbeddedResource', + description: 'Get prompt with embedded resource', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Prompts-Embedded-Resources', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources' + }] + }); + } + + return checks; + } +} + +export class PromptsGetWithImageScenario implements ClientScenario { + name = 'prompts-get-with-image'; + description = 'Test prompt with image content'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.getPrompt({ + name: 'test_prompt_with_image' + }); + + // Validate response + const errors: string[] = []; + if (!result.messages) errors.push('Missing messages array'); + + // Look for image content + const hasImageContent = result.messages.some((msg: any) => + msg.content?.type === 'image' && + msg.content?.data && + msg.content?.mimeType + ); + + if (!hasImageContent) { + errors.push('No image content found in prompt'); + } + + checks.push({ + id: 'prompts-get-with-image', + name: 'PromptsGetWithImage', + description: 'Get prompt with image content', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Prompts-Image', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content' + }], + details: { + messageCount: result.messages?.length || 0 + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'prompts-get-with-image', + name: 'PromptsGetWithImage', + description: 'Get prompt with image content', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Prompts-Image', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content' + }] + }); + } + + return checks; + } +} diff --git a/src/scenarios/server/resources.ts b/src/scenarios/server/resources.ts new file mode 100644 index 0000000..e1a9464 --- /dev/null +++ b/src/scenarios/server/resources.ts @@ -0,0 +1,365 @@ +/** + * Resources test scenarios for MCP servers + */ + +import { ClientScenario, ConformanceCheck } from '../../types.js'; +import { connectToServer } from './client-helper.js'; + +export class ResourcesListScenario implements ClientScenario { + name = 'resources-list'; + description = 'Test listing available resources'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.listResources(); + + // Validate response structure + const errors: string[] = []; + if (!result.resources) { + errors.push('Missing resources array'); + } else { + if (!Array.isArray(result.resources)) { + errors.push('resources is not an array'); + } + + result.resources.forEach((resource, index) => { + if (!resource.uri) errors.push(`Resource ${index}: missing uri`); + if (!resource.name) errors.push(`Resource ${index}: missing name`); + }); + } + + checks.push({ + id: 'resources-list', + name: 'ResourcesList', + description: 'Server lists available resources with valid structure', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Resources-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources' + }], + details: { + resourceCount: result.resources?.length || 0, + resources: result.resources?.map(r => r.uri) + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'resources-list', + name: 'ResourcesList', + description: 'Server lists available resources with valid structure', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Resources-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources' + }] + }); + } + + return checks; + } +} + +export class ResourcesReadTextScenario implements ClientScenario { + name = 'resources-read-text'; + description = 'Test reading text resource'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.readResource({ + uri: 'test://static-text' + }); + + // Validate response + const errors: string[] = []; + if (!result.contents) errors.push('Missing contents array'); + if (!Array.isArray(result.contents)) errors.push('contents is not an array'); + if (result.contents.length === 0) errors.push('contents array is empty'); + + const content = result.contents[0]; + if (content) { + if (!content.uri) errors.push('Content missing uri'); + if (!content.mimeType) errors.push('Content missing mimeType'); + if (!content.text) errors.push('Content missing text field'); + } + + checks.push({ + id: 'resources-read-text', + name: 'ResourcesReadText', + description: 'Read text resource successfully', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + }], + details: { + uri: content?.uri, + mimeType: content?.mimeType, + hasText: !!content?.text + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'resources-read-text', + name: 'ResourcesReadText', + description: 'Read text resource successfully', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + }] + }); + } + + return checks; + } +} + +export class ResourcesReadBinaryScenario implements ClientScenario { + name = 'resources-read-binary'; + description = 'Test reading binary resource'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.readResource({ + uri: 'test://static-binary' + }); + + // Validate response + const errors: string[] = []; + if (!result.contents) errors.push('Missing contents array'); + if (result.contents.length === 0) errors.push('contents array is empty'); + + const content = result.contents[0]; + if (content) { + if (!content.uri) errors.push('Content missing uri'); + if (!content.mimeType) errors.push('Content missing mimeType'); + if (!content.blob) errors.push('Content missing blob field'); + } + + checks.push({ + id: 'resources-read-binary', + name: 'ResourcesReadBinary', + description: 'Read binary resource successfully', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + }], + details: { + uri: content?.uri, + mimeType: content?.mimeType, + hasBlob: !!content?.blob + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'resources-read-binary', + name: 'ResourcesReadBinary', + description: 'Read binary resource successfully', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + }] + }); + } + + return checks; + } +} + +export class ResourcesTemplateReadScenario implements ClientScenario { + name = 'resources-templates-read'; + description = 'Test reading resource from template'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.readResource({ + uri: 'test://template/123/data' + }); + + // Validate response + const errors: string[] = []; + if (!result.contents) errors.push('Missing contents array'); + if (result.contents.length === 0) errors.push('contents array is empty'); + + const content = result.contents[0]; + if (content) { + if (!content.uri) errors.push('Content missing uri'); + if (!content.text && !content.blob) errors.push('Content missing text or blob'); + + // Check if parameter was substituted + const text = content.text || (content.blob ? '[binary]' : ''); + if (typeof text === 'string' && !text.includes('123')) { + errors.push('Parameter substitution not reflected in content'); + } + } + + checks.push({ + id: 'resources-templates-read', + name: 'ResourcesTemplateRead', + description: 'Read resource from template with parameter substitution', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Resources-Templates', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates' + }], + details: { + uri: content?.uri, + content: content?.text || content?.blob + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'resources-templates-read', + name: 'ResourcesTemplateRead', + description: 'Read resource from template with parameter substitution', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Resources-Templates', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates' + }] + }); + } + + return checks; + } +} + +export class ResourcesSubscribeScenario implements ClientScenario { + name = 'resources-subscribe'; + description = 'Test subscribing to resource updates'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + await connection.client.subscribeResource({ + uri: 'test://watched-resource' + }); + + checks.push({ + id: 'resources-subscribe', + name: 'ResourcesSubscribe', + description: 'Subscribe to resource successfully', + status: 'SUCCESS', + timestamp: new Date().toISOString(), + specReferences: [{ + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + }] + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'resources-subscribe', + name: 'ResourcesSubscribe', + description: 'Subscribe to resource successfully', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + }] + }); + } + + return checks; + } +} + +export class ResourcesUnsubscribeScenario implements ClientScenario { + name = 'resources-unsubscribe'; + description = 'Test unsubscribing from resource'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + // First subscribe + await connection.client.subscribeResource({ + uri: 'test://watched-resource' + }); + + // Then unsubscribe + await connection.client.unsubscribeResource({ + uri: 'test://watched-resource' + }); + + checks.push({ + id: 'resources-unsubscribe', + name: 'ResourcesUnsubscribe', + description: 'Unsubscribe from resource successfully', + status: 'SUCCESS', + timestamp: new Date().toISOString(), + specReferences: [{ + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + }] + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'resources-unsubscribe', + name: 'ResourcesUnsubscribe', + description: 'Unsubscribe from resource successfully', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + }] + }); + } + + return checks; + } +} diff --git a/src/scenarios/server/tools.ts b/src/scenarios/server/tools.ts new file mode 100644 index 0000000..da0aef2 --- /dev/null +++ b/src/scenarios/server/tools.ts @@ -0,0 +1,773 @@ +/** + * Tools test scenarios for MCP servers + */ + +import { ClientScenario, ConformanceCheck } from '../../types.js'; +import { connectToServer, NotificationCollector } from './client-helper.js'; +import { + ListToolsResultSchema, + CallToolResultSchema, + LoggingMessageNotificationSchema, + ProgressNotificationSchema, + CreateMessageRequestSchema, + ElicitRequestSchema, + Progress +} from '@modelcontextprotocol/sdk/types.js'; + +export class ToolsListScenario implements ClientScenario { + name = 'tools-list'; + description = 'Test listing available tools'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.listTools(); + + // Validate response structure + const errors: string[] = []; + if (!result.tools) { + errors.push('Missing tools array'); + } else { + if (!Array.isArray(result.tools)) { + errors.push('tools is not an array'); + } + + result.tools.forEach((tool, index) => { + if (!tool.name) errors.push(`Tool ${index}: missing name`); + if (!tool.description) errors.push(`Tool ${index}: missing description`); + if (!tool.inputSchema) errors.push(`Tool ${index}: missing inputSchema`); + }); + } + + checks.push({ + id: 'tools-list', + name: 'ToolsList', + description: 'Server lists available tools with valid structure', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Tools-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools' + }], + details: { + toolCount: result.tools?.length || 0, + tools: result.tools?.map(t => t.name) + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-list', + name: 'ToolsList', + description: 'Server lists available tools with valid structure', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Tools-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools' + }] + }); + } + + return checks; + } +} + +export class ToolsCallSimpleTextScenario implements ClientScenario { + name = 'tools-call-simple-text'; + description = 'Test calling a tool that returns simple text'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.callTool({ + name: 'test_simple_text', + arguments: {} + }); + + // Validate response + const errors: string[] = []; + const content = (result as any).content; + if (!content) errors.push('Missing content array'); + if (!Array.isArray(content)) errors.push('content is not an array'); + if (content && content.length === 0) errors.push('content array is empty'); + + const textContent = content && content.find((c: any) => c.type === 'text'); + if (!textContent) errors.push('No text content found'); + if (textContent && !textContent.text) errors.push('Text content missing text field'); + + checks.push({ + id: 'tools-call-simple-text', + name: 'ToolsCallSimpleText', + description: 'Tool returns simple text content', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }], + details: { + result + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-simple-text', + name: 'ToolsCallSimpleText', + description: 'Tool returns simple text content', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }] + }); + } + + return checks; + } +} + +export class ToolsCallImageScenario implements ClientScenario { + name = 'tools-call-image'; + description = 'Test calling a tool that returns image content'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.callTool({ + name: 'test_image_content', + arguments: {} + }); + + // Validate response + const errors: string[] = []; + const content = (result as any).content; + if (!content) errors.push('Missing content array'); + + const imageContent = content && content.find((c: any) => c.type === 'image'); + if (!imageContent) errors.push('No image content found'); + if (imageContent && !imageContent.data) errors.push('Image content missing data field'); + if (imageContent && !imageContent.mimeType) errors.push('Image content missing mimeType'); + + checks.push({ + id: 'tools-call-image', + name: 'ToolsCallImage', + description: 'Tool returns image content', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }], + details: { + mimeType: imageContent?.mimeType, + hasData: !!imageContent?.data + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-image', + name: 'ToolsCallImage', + description: 'Tool returns image content', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }] + }); + } + + return checks; + } +} + +export class ToolsCallMultipleContentTypesScenario implements ClientScenario { + name = 'tools-call-mixed-content'; + description = 'Test tool returning multiple content types'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.callTool({ + name: 'test_multiple_content_types', + arguments: {} + }); + + // Validate response + const errors: string[] = []; + const content = (result as any).content; + if (!content) errors.push('Missing content array'); + if (content && content.length < 2) errors.push('Expected multiple content items'); + + const hasText = content && content.some((c: any) => c.type === 'text'); + const hasImage = content && content.some((c: any) => c.type === 'image'); + const hasResource = content && content.some((c: any) => c.type === 'resource'); + + if (!hasText) errors.push('Missing text content'); + if (!hasImage) errors.push('Missing image content'); + if (!hasResource) errors.push('Missing resource content'); + + checks.push({ + id: 'tools-call-mixed-content', + name: 'ToolsCallMixedContent', + description: 'Tool returns multiple content types', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }], + details: { + contentCount: content ? content.length : 0, + contentTypes: content ? content.map((c: any) => c.type) : [] + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-mixed-content', + name: 'ToolsCallMixedContent', + description: 'Tool returns multiple content types', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }] + }); + } + + return checks; + } +} + +export class ToolsCallWithLoggingScenario implements ClientScenario { + name = 'tools-call-with-logging'; + description = 'Test tool that sends log messages during execution'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + const notifications = new NotificationCollector(connection.client); + + const result = await connection.client.callTool({ + name: 'test_tool_with_logging', + arguments: {} + }); + + // Wait a bit for notifications to arrive + await new Promise(resolve => setTimeout(resolve, 200)); + + // Check for log notifications from the NotificationCollector + const logNotifications = notifications.getNotifications(); + + const errors: string[] = []; + if (logNotifications.length === 0) { + errors.push('No log notifications received'); + } else if (logNotifications.length < 3) { + errors.push(`Expected at least 3 log messages, got ${logNotifications.length}`); + } + + checks.push({ + id: 'tools-call-with-logging', + name: 'ToolsCallWithLogging', + description: 'Tool sends log messages during execution', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + }], + details: { + logCount: logNotifications.length, + logs: logNotifications.map((n: any) => n.params) + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-with-logging', + name: 'ToolsCallWithLogging', + description: 'Tool sends log messages during execution', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + }] + }); + } + + return checks; + } +} + +export class ToolsCallErrorScenario implements ClientScenario { + name = 'tools-call-error'; + description = 'Test tool error reporting'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result: any = await connection.client.callTool({ + name: 'test_error_handling', + arguments: {} + }); + + // Check if result has isError flag + const hasIsError = result.isError === true; + const hasContent = result.content && result.content.length > 0; + const hasErrorMessage = hasContent && result.content[0].text; + + checks.push({ + id: 'tools-call-error', + name: 'ToolsCallError', + description: 'Tool returns error correctly', + status: (hasIsError && hasErrorMessage) ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: !hasIsError ? 'Tool did not return isError: true' : + !hasErrorMessage ? 'Error result missing error message' : undefined, + specReferences: [{ + id: 'MCP-Error-Handling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' + }], + details: { + result + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-error', + name: 'ToolsCallError', + description: 'Tool returns error correctly', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Error-Handling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' + }] + }); + } + + return checks; + } +} + +export class ToolsCallWithProgressScenario implements ClientScenario { + name = 'tools-call-with-progress'; + description = 'Test tool that reports progress notifications'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + const progressUpdates: Array = []; + // TODO: investigate why await connection.client.callTool didn't work for progress. + const result = await connection.client.request( + { + method: 'tools/call', + params: { + name: 'test_tool_with_progress', + arguments: {}, + _meta: { + progressToken: 'progress-test-1' + } + } + }, + CallToolResultSchema, + { + onprogress: progress => { + progressUpdates.push(progress); + } + } + ); + + const errors: string[] = []; + if (progressUpdates.length === 0) { + errors.push('No progress notifications received'); + } else if (progressUpdates.length < 3) { + errors.push(`Expected at least 3 progress notifications, got ${progressUpdates.length}`); + } + + if (progressUpdates.length >= 3) { + const progress0 = progressUpdates[0].progress; + const progress1 = progressUpdates[1].progress; + const progress2 = progressUpdates[2].progress; + + if (!(progress0 <= progress1 && progress1 <= progress2)) { + errors.push('Progress values should be increasing'); + } + } + + checks.push({ + id: 'tools-call-with-progress', + name: 'ToolsCallWithProgress', + description: 'Tool reports progress notifications', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Progress', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress' + }], + details: { + progressCount: progressUpdates.length, + progressNotifications: progressUpdates.map((n: Progress) => n), + result + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-with-progress', + name: 'ToolsCallWithProgress', + description: 'Tool reports progress notifications', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Progress', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress' + }] + }); + } + + return checks; + } +} + +export class ToolsCallSamplingScenario implements ClientScenario { + name = 'tools-call-sampling'; + description = 'Test tool that requests LLM sampling from client'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + let samplingRequested = false; + connection.client.setRequestHandler( + CreateMessageRequestSchema, + async (_request) => { + samplingRequested = true; + return { + role: 'assistant', + content: { + type: 'text', + text: 'This is a test response from the client' + }, + model: 'test-model', + stopReason: 'endTurn' + }; + } + ); + + const result = await connection.client.callTool({ + name: 'test_sampling', + arguments: { + prompt: 'Test prompt for sampling' + } + }); + + const errors: string[] = []; + if (!samplingRequested) { + errors.push('Server did not request sampling from client'); + } + + const content = (result as any).content; + if (!content || content.length === 0) { + errors.push('Tool did not return content'); + } + + checks.push({ + id: 'tools-call-sampling', + name: 'ToolsCallSampling', + description: 'Tool requests LLM sampling from client', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Sampling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling' + }], + details: { + samplingRequested, + result + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-sampling', + name: 'ToolsCallSampling', + description: 'Tool requests LLM sampling from client', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Sampling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling' + }] + }); + } + + return checks; + } +} + +export class ToolsCallElicitationScenario implements ClientScenario { + name = 'tools-call-elicitation'; + description = 'Test tool that requests user input (elicitation) from client'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + let elicitationRequested = false; + connection.client.setRequestHandler( + ElicitRequestSchema, + async (_request) => { + elicitationRequested = true; + return { + action: 'accept', + content: { + username: 'testuser', + email: 'test@example.com' + } + }; + } + ); + + const result = await connection.client.callTool({ + name: 'test_elicitation', + arguments: { + message: 'Please provide your information' + } + }); + + const errors: string[] = []; + if (!elicitationRequested) { + errors.push('Server did not request elicitation from client'); + } + + const content = (result as any).content; + if (!content || content.length === 0) { + errors.push('Tool did not return content'); + } + + checks.push({ + id: 'tools-call-elicitation', + name: 'ToolsCallElicitation', + description: 'Tool requests user input from client', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Elicitation', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation' + }], + details: { + elicitationRequested, + result + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-elicitation', + name: 'ToolsCallElicitation', + description: 'Tool requests user input from client', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Elicitation', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation' + }] + }); + } + + return checks; + } +} + +export class ToolsCallAudioScenario implements ClientScenario { + name = 'tools-call-audio'; + description = 'Test calling a tool that returns audio content'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.callTool({ + name: 'test_audio_content', + arguments: {} + }); + + // Validate response + const errors: string[] = []; + const content = (result as any).content; + if (!content) errors.push('Missing content array'); + if (!Array.isArray(content)) errors.push('content is not an array'); + if (content && content.length === 0) errors.push('content array is empty'); + + const audioContent = content && content.find((c: any) => c.type === 'audio'); + if (!audioContent) errors.push('No audio content found'); + if (audioContent && !audioContent.data) errors.push('Audio content missing data field'); + if (audioContent && !audioContent.mimeType) errors.push('Audio content missing mimeType field'); + if (audioContent && audioContent.mimeType !== 'audio/wav') errors.push(`Expected mimeType 'audio/wav', got '${audioContent.mimeType}'`); + + checks.push({ + id: 'tools-call-audio', + name: 'ToolsCallAudio', + description: 'Tool returns audio content', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }], + details: { + hasAudioContent: !!audioContent, + audioDataLength: audioContent?.data?.length || 0 + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-audio', + name: 'ToolsCallAudio', + description: 'Tool returns audio content', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }] + }); + } + + return checks; + } +} + +export class ToolsCallEmbeddedResourceScenario implements ClientScenario { + name = 'tools-call-embedded-resource'; + description = 'Test calling a tool that returns embedded resource content'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + const result = await connection.client.callTool({ + name: 'test_embedded_resource', + arguments: {} + }); + + // Validate response + const errors: string[] = []; + const content = (result as any).content; + if (!content) errors.push('Missing content array'); + if (!Array.isArray(content)) errors.push('content is not an array'); + if (content && content.length === 0) errors.push('content array is empty'); + + const resourceContent = content && content.find((c: any) => c.type === 'resource'); + if (!resourceContent) errors.push('No resource content found'); + if (resourceContent && !resourceContent.resource) errors.push('Resource content missing resource field'); + if (resourceContent?.resource) { + if (!resourceContent.resource.uri) errors.push('Resource missing uri field'); + if (!resourceContent.resource.mimeType) errors.push('Resource missing mimeType field'); + if (!resourceContent.resource.text && !resourceContent.resource.blob) { + errors.push('Resource missing both text and blob fields'); + } + } + + checks.push({ + id: 'tools-call-embedded-resource', + name: 'ToolsCallEmbeddedResource', + description: 'Tool returns embedded resource content', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }], + details: { + hasResourceContent: !!resourceContent, + resourceUri: resourceContent?.resource?.uri + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'tools-call-embedded-resource', + name: 'ToolsCallEmbeddedResource', + description: 'Tool returns embedded resource content', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + }] + }); + } + + return checks; + } +} diff --git a/src/scenarios/server/utils.ts b/src/scenarios/server/utils.ts new file mode 100644 index 0000000..f705bc6 --- /dev/null +++ b/src/scenarios/server/utils.ts @@ -0,0 +1,130 @@ +/** + * Utilities test scenarios for MCP servers + */ + +import { ClientScenario, ConformanceCheck } from '../../types.js'; +import { connectToServer } from './client-helper.js'; + +export class LoggingSetLevelScenario implements ClientScenario { + name = 'logging-set-level'; + description = 'Test setting logging level'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + // Send logging/setLevel request + const result = await connection.client.setLoggingLevel('info'); + + // Validate response (should return empty object {}) + const errors: string[] = []; + if (result && Object.keys(result).length > 0) { + errors.push('Expected empty object {} response'); + } + + checks.push({ + id: 'logging-set-level', + name: 'LoggingSetLevel', + description: 'Server accepts logging level setting', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + }], + details: { + result + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'logging-set-level', + name: 'LoggingSetLevel', + description: 'Server accepts logging level setting', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + }] + }); + } + + return checks; + } +} + +export class CompletionCompleteScenario implements ClientScenario { + name = 'completion-complete'; + description = 'Test completion endpoint'; + + async run(serverUrl: string): Promise { + const checks: ConformanceCheck[] = []; + + try { + const connection = await connectToServer(serverUrl); + + // Send completion/complete request + const result = await connection.client.complete( + { + ref: { + type: 'ref/prompt', + name: 'test_prompt_with_arguments' + }, + argument: { + name: 'arg1', + value: 'test' + } + } + ); + + // Validate response structure + const errors: string[] = []; + if (!result.completion) { + errors.push('Missing completion field'); + } else { + if (!result.completion.values) errors.push('Missing values array in completion'); + if (!Array.isArray(result.completion.values)) errors.push('completion.values is not an array'); + } + + checks.push({ + id: 'completion-complete', + name: 'CompletionComplete', + description: 'Server responds to completion requests', + status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: errors.length > 0 ? errors.join('; ') : undefined, + specReferences: [{ + id: 'MCP-Completion', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion' + }], + details: { + result + } + }); + + await connection.close(); + } catch (error) { + checks.push({ + id: 'completion-complete', + name: 'CompletionComplete', + description: 'Server responds to completion requests', + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, + specReferences: [{ + id: 'MCP-Completion', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion' + }] + }); + } + + return checks; + } +} diff --git a/src/server-runner.ts b/src/server-runner.ts index 2ca5c19..c97fb89 100644 --- a/src/server-runner.ts +++ b/src/server-runner.ts @@ -48,6 +48,7 @@ async function main(): Promise { const args = process.argv.slice(2); let serverUrl: string | null = null; let scenario: string | null = null; + let runAll = false; for (let i = 0; i < args.length; i++) { if (args[i] === '--server-url' && i + 1 < args.length) { @@ -56,40 +57,97 @@ async function main(): Promise { } else if (args[i] === '--scenario' && i + 1 < args.length) { scenario = args[i + 1]; i++; + } else if (args[i] === '--all') { + runAll = true; } } - if (!serverUrl || !scenario) { - console.error('Usage: server-runner --server-url --scenario '); - console.error('Example: server-runner --server-url http://localhost:3000 --scenario initialize'); + if (!serverUrl) { + console.error('Usage: server-runner --server-url [--scenario | --all]'); + console.error('Example: server-runner --server-url http://localhost:3000/mcp --scenario server-initialize'); + console.error('Example: server-runner --server-url http://localhost:3000/mcp --all'); + process.exit(1); + } + + if (!scenario && !runAll) { + console.error('Must specify either --scenario or --all'); process.exit(1); } try { - const result = await runServerConformanceTest(serverUrl, scenario); - - const denominator = result.checks.filter(c => c.status === 'SUCCESS' || c.status == 'FAILURE').length; - const passed = result.checks.filter(c => c.status === 'SUCCESS').length; - const failed = result.checks.filter(c => c.status === 'FAILURE').length; - - console.log(`Checks:\n${JSON.stringify(result.checks, null, 2)}`); - - console.log(`\nTest Results:`); - console.log(`Passed: ${passed}/${denominator}, ${failed} failed`); - - if (failed > 0) { - console.log('\nFailed Checks:'); - result.checks - .filter(c => c.status === 'FAILURE') - .forEach(c => { - console.log(` - ${c.name}: ${c.description}`); - if (c.errorMessage) { - console.log(` Error: ${c.errorMessage}`); - } - }); + let allResults: { scenario: string; checks: ConformanceCheck[] }[] = []; + + if (runAll) { + // Get all server scenarios + const { listClientScenarios } = await import('./scenarios/index.js'); + const scenarios = listClientScenarios(); + + console.log(`Running ${scenarios.length} scenarios against ${serverUrl}\n`); + + for (const scenarioName of scenarios) { + console.log(`\n=== Running scenario: ${scenarioName} ===`); + try { + const result = await runServerConformanceTest(serverUrl, scenarioName); + allResults.push({ scenario: scenarioName, checks: result.checks }); + } catch (error) { + console.error(`Failed to run scenario ${scenarioName}:`, error); + allResults.push({ + scenario: scenarioName, + checks: [{ + id: scenarioName, + name: scenarioName, + description: `Failed to run scenario`, + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: error instanceof Error ? error.message : String(error) + }] + }); + } + } + + // Summary + console.log('\n\n=== SUMMARY ==='); + let totalPassed = 0; + let totalFailed = 0; + + for (const result of allResults) { + const passed = result.checks.filter(c => c.status === 'SUCCESS').length; + const failed = result.checks.filter(c => c.status === 'FAILURE').length; + totalPassed += passed; + totalFailed += failed; + + const status = failed === 0 ? '✓' : '✗'; + console.log(`${status} ${result.scenario}: ${passed} passed, ${failed} failed`); + } + + console.log(`\nTotal: ${totalPassed} passed, ${totalFailed} failed`); + process.exit(totalFailed > 0 ? 1 : 0); + } else if (scenario) { + const result = await runServerConformanceTest(serverUrl, scenario); + + const denominator = result.checks.filter(c => c.status === 'SUCCESS' || c.status == 'FAILURE').length; + const passed = result.checks.filter(c => c.status === 'SUCCESS').length; + const failed = result.checks.filter(c => c.status === 'FAILURE').length; + + console.log(`Checks:\n${JSON.stringify(result.checks, null, 2)}`); + + console.log(`\nTest Results:`); + console.log(`Passed: ${passed}/${denominator}, ${failed} failed`); + + if (failed > 0) { + console.log('\nFailed Checks:'); + result.checks + .filter(c => c.status === 'FAILURE') + .forEach(c => { + console.log(` - ${c.name}: ${c.description}`); + if (c.errorMessage) { + console.log(` Error: ${c.errorMessage}`); + } + }); + } + + process.exit(failed > 0 ? 1 : 0); } - - process.exit(failed > 0 ? 1 : 0); } catch (error) { console.error('Server test runner error:', error); process.exit(1); diff --git a/test-tool-call.ts b/test-tool-call.ts new file mode 100644 index 0000000..1f348ed --- /dev/null +++ b/test-tool-call.ts @@ -0,0 +1,39 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; + +async function testToolCall() { + const client = new Client( + { + name: 'debug-client', + version: '1.0.0' + }, + { + capabilities: { + sampling: {} + } + } + ); + + const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); + + try { + console.log('Connecting...'); + await client.connect(transport); + console.log('Connected successfully'); + + console.log('\nCalling test_simple_text tool...'); + const result = await client.callTool({ + name: 'test_simple_text', + arguments: {} + }); + + console.log('Result:', JSON.stringify(result, null, 2)); + + await client.close(); + } catch (error) { + console.error('Error:', error); + process.exit(1); + } +} + +testToolCall(); From 00421fcebd1e68cd06ba680a6612e27dcf6a6503 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 3 Nov 2025 18:10:48 +0000 Subject: [PATCH 3/5] some clean up --- src/checks/server.ts | 36 ---------------------- src/scenarios/client/tools_call.ts | 6 ++-- src/scenarios/index.ts | 8 +---- src/scenarios/server/lifecycle.ts | 48 ------------------------------ test-tool-call.ts | 39 ------------------------ 5 files changed, 4 insertions(+), 133 deletions(-) delete mode 100644 test-tool-call.ts diff --git a/src/checks/server.ts b/src/checks/server.ts index 894448b..df82fc0 100644 --- a/src/checks/server.ts +++ b/src/checks/server.ts @@ -1,37 +1 @@ import { ConformanceCheck, CheckStatus } from '../types.js'; - -export function createServerInitializationCheck(initializeResponse: any, expectedSpecVersion: string = '2025-06-18'): ConformanceCheck { - const result = initializeResponse?.result; - const protocolVersion = result?.protocolVersion; - const serverInfo = result?.serverInfo; - const capabilities = result?.capabilities; - - const errors: string[] = []; - if (!initializeResponse?.jsonrpc) errors.push('Missing jsonrpc field'); - if (!initializeResponse?.id) errors.push('Missing id field'); - if (!result) errors.push('Missing result field'); - if (!protocolVersion) errors.push('Missing protocolVersion in result'); - if (protocolVersion !== expectedSpecVersion) - errors.push(`Protocol version mismatch: expected ${expectedSpecVersion}, got ${protocolVersion}`); - if (!serverInfo) errors.push('Missing serverInfo in result'); - if (!serverInfo?.name) errors.push('Missing server name in serverInfo'); - if (!serverInfo?.version) errors.push('Missing server version in serverInfo'); - if (capabilities === undefined) errors.push('Missing capabilities in result'); - - const status: CheckStatus = errors.length === 0 ? 'SUCCESS' : 'FAILURE'; - - return { - id: 'mcp-server-initialization', - name: 'MCPServerInitialization', - description: 'Validates that MCP server properly responds to initialize request', - status, - timestamp: new Date().toISOString(), - specReferences: [{ id: 'MCP-Lifecycle', url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' }], - details: { - expectedSpecVersion, - response: initializeResponse - }, - errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - logs: errors.length > 0 ? errors : undefined - }; -} diff --git a/src/scenarios/client/tools_call.ts b/src/scenarios/client/tools_call.ts index 4c45af4..6ec2f3b 100644 --- a/src/scenarios/client/tools_call.ts +++ b/src/scenarios/client/tools_call.ts @@ -2,7 +2,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import type { Scenario, ConformanceCheck } from '../../types.js'; -import express from 'express'; +import express, { Request, Response, NextFunction } from 'express'; import { ScenarioUrls } from '../../types.js'; function createServer(checks: ConformanceCheck[]): express.Application { @@ -83,7 +83,7 @@ function createServer(checks: ConformanceCheck[]): express.Application { const app = express(); app.use(express.json()); - app.use((req, res, next) => { + app.use((req: Request, res: Response, next: NextFunction) => { // Log incoming requests for debugging // console.log(`Incoming request: ${req.method} ${req.url}`); checks.push({ @@ -111,7 +111,7 @@ function createServer(checks: ConformanceCheck[]): express.Application { }); }); - app.post('/mcp', async (req, res) => { + app.post('/mcp', async (req: Request, res: Response) => { const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); diff --git a/src/scenarios/index.ts b/src/scenarios/index.ts index 7bdd9e6..e5fd364 100644 --- a/src/scenarios/index.ts +++ b/src/scenarios/index.ts @@ -1,12 +1,10 @@ import { Scenario, ClientScenario } from '../types.js'; import { InitializeScenario } from './client/initialize.js'; import { ToolsCallScenario } from './client/tools_call.js'; -import { ServerInitializeClientScenario } from './server/server_initialize.js'; // Import all new server test scenarios import { - ServerInitializeScenario, - ServerInitializeCapabilitiesScenario + ServerInitializeScenario } from './server/lifecycle.js'; import { @@ -51,12 +49,8 @@ export const scenarios = new Map([ ]); export const clientScenarios = new Map([ - // Legacy - ['initialize', new ServerInitializeClientScenario()], - // Lifecycle scenarios ['server-initialize', new ServerInitializeScenario()], - ['server-initialize-capabilities', new ServerInitializeCapabilitiesScenario()], // Utilities scenarios ['logging-set-level', new LoggingSetLevelScenario()], diff --git a/src/scenarios/server/lifecycle.ts b/src/scenarios/server/lifecycle.ts index 349b544..8a82cc7 100644 --- a/src/scenarios/server/lifecycle.ts +++ b/src/scenarios/server/lifecycle.ts @@ -52,51 +52,3 @@ export class ServerInitializeScenario implements ClientScenario { return checks; } } - -export class ServerInitializeCapabilitiesScenario implements ClientScenario { - name = 'server-initialize-capabilities'; - description = 'Test server advertising all its capabilities'; - - async run(serverUrl: string): Promise { - const checks: ConformanceCheck[] = []; - - try { - const connection = await connectToServer(serverUrl); - - // Get server capabilities from the connection - // Note: The SDK should expose this, for now we'll just verify connection works - checks.push({ - id: 'server-initialize-capabilities', - name: 'ServerInitializeCapabilities', - description: 'Server declares its capabilities correctly', - status: 'SUCCESS', - timestamp: new Date().toISOString(), - specReferences: [{ - id: 'MCP-Capabilities', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/architecture/index#capability-negotiation' - }], - details: { - serverUrl, - note: 'Capabilities verified through successful connection' - } - }); - - await connection.close(); - } catch (error) { - checks.push({ - id: 'server-initialize-capabilities', - name: 'ServerInitializeCapabilities', - description: 'Server declares its capabilities correctly', - status: 'FAILURE', - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Capabilities', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/architecture/index#capability-negotiation' - }] - }); - } - - return checks; - } -} diff --git a/test-tool-call.ts b/test-tool-call.ts deleted file mode 100644 index 1f348ed..0000000 --- a/test-tool-call.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; - -async function testToolCall() { - const client = new Client( - { - name: 'debug-client', - version: '1.0.0' - }, - { - capabilities: { - sampling: {} - } - } - ); - - const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); - - try { - console.log('Connecting...'); - await client.connect(transport); - console.log('Connected successfully'); - - console.log('\nCalling test_simple_text tool...'); - const result = await client.callTool({ - name: 'test_simple_text', - arguments: {} - }); - - console.log('Result:', JSON.stringify(result, null, 2)); - - await client.close(); - } catch (error) { - console.error('Error:', error); - process.exit(1); - } -} - -testToolCall(); From 7b3666a71471c71db3ff4f3d02fc7011fe4ee767 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 3 Nov 2025 18:19:20 +0000 Subject: [PATCH 4/5] lint etc. --- README.md | 2 + SERVER_REQUIREMENTS.md | 379 ++++++++++-------- examples/servers/typescript/README.md | 11 + .../servers/typescript/everything-server.ts | 280 ++++++------- examples/servers/typescript/tsconfig.json | 4 +- src/checks/index.ts | 4 +- src/checks/server.ts | 1 - src/scenarios/client/tools_call.ts | 1 + src/scenarios/index.ts | 9 +- src/scenarios/server/client-helper.ts | 18 +- src/scenarios/server/lifecycle.ts | 20 +- src/scenarios/server/prompts.ts | 111 ++--- src/scenarios/server/resources.ts | 120 +++--- src/scenarios/server/tools.ts | 294 ++++++++------ src/scenarios/server/utils.ts | 60 +-- src/server-runner.ts | 20 +- 16 files changed, 745 insertions(+), 589 deletions(-) diff --git a/README.md b/README.md index 15e2e94..502a011 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,14 @@ A framework for testing MCP (Model Context Protocol) client implementations agai ## Quick Start ### Testing Clients + ```bash npm install npm run start -- --command "tsx examples/clients/typescript/test1.ts" --scenario initialize ``` ### Testing Servers + ```bash npm run test:server -- --server-url http://localhost:3000/mcp --all ``` diff --git a/SERVER_REQUIREMENTS.md b/SERVER_REQUIREMENTS.md index e3f7501..2e32517 100644 --- a/SERVER_REQUIREMENTS.md +++ b/SERVER_REQUIREMENTS.md @@ -5,6 +5,7 @@ This document specifies the requirements for building an MCP "Everything Server" ## Purpose The Everything Server is a reference implementation that: + - Demonstrates all MCP server features in a single, testable server - Uses standardized naming conventions for tools, resources, and prompts - Enables automated conformance testing across different SDK implementations @@ -13,6 +14,7 @@ The Everything Server is a reference implementation that: ## Protocol Version + **Target MCP Specification**: `2025-06-18` ## Transport @@ -30,6 +32,7 @@ This document specifies requirements based on the MCP specification. All feature ### Server Identity Your server MUST provide: + ```typescript { name: "mcp-conformance-test-server", // Or your SDK-specific name @@ -71,19 +74,23 @@ Your server MUST declare these capabilities during initialization: **Endpoint**: `initialize` **Requirements**: + - Accept `initialize` request with client info and capabilities - Return server info, protocol version, and capabilities - Protocol version MUST be `"2025-06-18"` **Example Response**: + ```json { - "protocolVersion": "2025-06-18", - "serverInfo": { - "name": "mcp-conformance-test-server", - "version": "1.0.0" - }, - "capabilities": { /* as above */ } + "protocolVersion": "2025-06-18", + "serverInfo": { + "name": "mcp-conformance-test-server", + "version": "1.0.0" + }, + "capabilities": { + /* as above */ + } } ``` @@ -92,6 +99,7 @@ Your server MUST declare these capabilities during initialization: **Notification**: `initialized` **Requirements**: + - Accept `initialized` notification from client after handshake - No response required (it's a notification) - Server should be ready for requests after receiving this @@ -105,17 +113,19 @@ Your server MUST declare these capabilities during initialization: **Endpoint**: `tools/list` **Requirements**: + - Return array of all available tools - Each tool MUST have: - - `name` (string) - - `description` (string) - - `inputSchema` (valid JSON Schema object) + - `name` (string) + - `description` (string) + - `inputSchema` (valid JSON Schema object) ### 2.2. Call Tool **Endpoint**: `tools/call` **Requirements**: + - Accept tool name and arguments - Execute tool and return result - Result MUST have `content` array @@ -130,11 +140,10 @@ Implement these tools with exact names: **Arguments**: None **Returns**: Text content + ```typescript { - content: [ - { type: "text", text: "This is a simple text response for testing." } - ] + content: [{ type: 'text', text: 'This is a simple text response for testing.' }]; } ``` @@ -143,15 +152,16 @@ Implement these tools with exact names: **Arguments**: None **Returns**: Image content with base64 data + ```typescript { - content: [ - { - type: "image", - data: "", - mimeType: "image/png" - } - ] + content: [ + { + type: 'image', + data: '', + mimeType: 'image/png' + } + ]; } ``` @@ -162,15 +172,16 @@ Implement these tools with exact names: **Arguments**: None **Returns**: Audio content with base64 data + ```typescript { - content: [ - { - type: "audio", - data: "", - mimeType: "audio/wav" - } - ] + content: [ + { + type: 'audio', + data: '', + mimeType: 'audio/wav' + } + ]; } ``` @@ -181,18 +192,19 @@ Implement these tools with exact names: **Arguments**: None **Returns**: Embedded resource content + ```typescript { - content: [ - { - type: "resource", - resource: { - uri: "test://embedded-resource", - mimeType: "text/plain", - text: "This is an embedded resource content." - } - } - ] + content: [ + { + type: 'resource', + resource: { + uri: 'test://embedded-resource', + mimeType: 'text/plain', + text: 'This is an embedded resource content.' + } + } + ]; } ``` @@ -201,20 +213,21 @@ Implement these tools with exact names: **Arguments**: None **Returns**: Multiple content items (text + image + resource) + ```typescript { - content: [ - { type: "text", text: "Multiple content types test:" }, - { type: "image", data: "", mimeType: "image/png" }, - { - type: "resource", - resource: { - uri: "test://mixed-content-resource", - mimeType: "application/json", - text: "{\"test\":\"data\",\"value\":123}" - } - } - ] + content: [ + { type: 'text', text: 'Multiple content types test:' }, + { type: 'image', data: '', mimeType: 'image/png' }, + { + type: 'resource', + resource: { + uri: 'test://mixed-content-resource', + mimeType: 'application/json', + text: '{"test":"data","value":123}' + } + } + ]; } ``` @@ -223,6 +236,7 @@ Implement these tools with exact names: **Arguments**: None **Behavior**: During execution, send 3 log notifications at info level: + 1. "Tool execution started" 2. "Tool processing data" (after ~50ms delay) 3. "Tool execution completed" (after another ~50ms delay) @@ -236,6 +250,7 @@ Implement these tools with exact names: **Arguments**: None **Behavior**: If `_meta.progressToken` is provided in request: + - Send progress notification: `0/100` - Wait ~50ms - Send progress notification: `50/100` @@ -247,6 +262,7 @@ If no progress token provided, just execute with delays. **Returns**: Text content confirming execution **Progress Notification Format**: + ```typescript { method: "notifications/progress", @@ -265,23 +281,26 @@ If no progress token provided, just execute with delays. **Behavior**: Always throw an error **Returns**: JSON-RPC error + ```json { - "error": { - "code": -32000, - "message": "This tool intentionally returns an error for testing" - } + "error": { + "code": -32000, + "message": "This tool intentionally returns an error for testing" + } } ``` #### `test_sampling` **Arguments**: + - `prompt` (string, required) - The prompt to send to the LLM **Behavior**: Request LLM sampling from the client using `sampling/createMessage` **Sampling Request**: + ```typescript { "method": "sampling/createMessage", @@ -298,6 +317,7 @@ If no progress token provided, just execute with delays. ``` **Returns**: Text content with the LLM's response + ```typescript { "content": [ @@ -314,11 +334,13 @@ If no progress token provided, just execute with delays. #### `test_elicitation` **Arguments**: + - `message` (string, required) - The message to show the user **Behavior**: Request user input from the client using `elicitation/create` **Elicitation Request**: + ```typescript { "method": "elicitation/create", @@ -339,6 +361,7 @@ If no progress token provided, just execute with delays. ``` **Returns**: Text content with the user's response + ```typescript { "content": [ @@ -361,13 +384,14 @@ If no progress token provided, just execute with delays. **Endpoint**: `resources/list` **Requirements**: + - Return array of all available **direct resources** (not templates) - Support optional cursor-based pagination (see Section 6.1) - Each resource MUST have: - - `uri` (string) - - `name` (string) - - `description` (string) - - `mimeType` (string, optional) + - `uri` (string) + - `name` (string) + - `description` (string) + - `mimeType` (string, optional) **Note**: Resource templates are listed via separate `resources/templates/list` endpoint (see 3.1a) @@ -376,19 +400,21 @@ If no progress token provided, just execute with delays. **Endpoint**: `resources/templates/list` **Requirements**: + - Return array of all available **resource templates** - Support optional cursor-based pagination (see Section 6.1) - Each template MUST have: - - `uriTemplate` (string) - RFC 6570 URI template - - `name` (string) - - `description` (string) - - `mimeType` (string, optional) + - `uriTemplate` (string) - RFC 6570 URI template + - `name` (string) + - `description` (string) + - `mimeType` (string, optional) ### 3.2. Read Resource **Endpoint**: `resources/read` **Requirements**: + - Accept resource URI - Return resource contents - Response MUST have `contents` array @@ -403,6 +429,7 @@ Implement these resources with exact URIs: **Type**: Static text resource **Metadata** (for `resources/list`): + ```typescript { uri: "test://static-text", @@ -413,15 +440,16 @@ Implement these resources with exact URIs: ``` **Content** (for `resources/read`): + ```typescript { - contents: [ - { - uri: "test://static-text", - mimeType: "text/plain", - text: "This is the content of the static text resource." - } - ] + contents: [ + { + uri: 'test://static-text', + mimeType: 'text/plain', + text: 'This is the content of the static text resource.' + } + ]; } ``` @@ -430,6 +458,7 @@ Implement these resources with exact URIs: **Type**: Static binary resource (image) **Metadata**: + ```typescript { uri: "test://static-binary", @@ -440,15 +469,16 @@ Implement these resources with exact URIs: ``` **Content**: + ```typescript { - contents: [ - { - uri: "test://static-binary", - mimeType: "image/png", - blob: "" - } - ] + contents: [ + { + uri: 'test://static-binary', + mimeType: 'image/png', + blob: '' + } + ]; } ``` @@ -457,6 +487,7 @@ Implement these resources with exact URIs: **Type**: Resource template with parameter **Metadata** (for `resources/list`): + ```typescript { uriTemplate: "test://template/{id}/data", @@ -469,15 +500,16 @@ Implement these resources with exact URIs: **Behavior**: When client requests `test://template/123/data`, substitute `{id}` with `123` **Content** (for `resources/read` with `uri: "test://template/123/data"`): + ```typescript { - contents: [ - { - uri: "test://template/123/data", - mimeType: "application/json", - text: "{\"id\":\"123\",\"templateTest\":true,\"data\":\"Data for ID: 123\"}" - } - ] + contents: [ + { + uri: 'test://template/123/data', + mimeType: 'application/json', + text: '{"id":"123","templateTest":true,"data":"Data for ID: 123"}' + } + ]; } ``` @@ -488,6 +520,7 @@ Implement these resources with exact URIs: **Type**: Subscribable resource **Metadata**: + ```typescript { uri: "test://watched-resource", @@ -498,15 +531,16 @@ Implement these resources with exact URIs: ``` **Content**: + ```typescript { - contents: [ - { - uri: "test://watched-resource", - mimeType: "text/plain", - text: "Watched resource content" - } - ] + contents: [ + { + uri: 'test://watched-resource', + mimeType: 'text/plain', + text: 'Watched resource content' + } + ]; } ``` @@ -515,24 +549,27 @@ Implement these resources with exact URIs: **Endpoint**: `resources/subscribe` **Requirements**: + - Accept subscription request with URI - Track subscribed URIs - Send `notifications/resources/updated` when subscribed resources change - Return empty object `{}` **Example Request**: + ```json { - "method": "resources/subscribe", - "params": { - "uri": "test://watched-resource" - } + "method": "resources/subscribe", + "params": { + "uri": "test://watched-resource" + } } ``` **Endpoint**: `resources/unsubscribe` **Requirements**: + - Accept unsubscribe request with URI - Remove URI from subscriptions - Stop sending update notifications for that URI @@ -547,17 +584,19 @@ Implement these resources with exact URIs: **Endpoint**: `prompts/list` **Requirements**: + - Return array of all available prompts - Each prompt MUST have: - - `name` (string) - - `description` (string) - - `arguments` (array, optional) - list of required arguments + - `name` (string) + - `description` (string) + - `arguments` (array, optional) - list of required arguments ### 4.2. Get Prompt **Endpoint**: `prompts/get` **Requirements**: + - Accept prompt name and arguments - Return prompt messages - Response MUST have `messages` array @@ -572,27 +611,30 @@ Implement these prompts with exact names: **Arguments**: None **Returns**: + ```typescript { - messages: [ - { - role: "user", - content: { - type: "text", - text: "This is a simple prompt for testing." - } - } - ] + messages: [ + { + role: 'user', + content: { + type: 'text', + text: 'This is a simple prompt for testing.' + } + } + ]; } ``` #### `test_prompt_with_arguments` **Arguments**: + - `arg1` (string, required) - First test argument - `arg2` (string, required) - Second test argument **Metadata** (for `prompts/list`): + ```typescript { name: "test_prompt_with_arguments", @@ -605,48 +647,51 @@ Implement these prompts with exact names: ``` **Returns** (with args `{arg1: "hello", arg2: "world"}`): + ```typescript { - messages: [ - { - role: "user", - content: { - type: "text", - text: "Prompt with arguments: arg1='hello', arg2='world'" - } - } - ] + messages: [ + { + role: 'user', + content: { + type: 'text', + text: "Prompt with arguments: arg1='hello', arg2='world'" + } + } + ]; } ``` #### `test_prompt_with_embedded_resource` **Arguments**: + - `resourceUri` (string, required) - URI of the resource to embed **Returns**: + ```typescript { - messages: [ - { - role: "user", - content: { - type: "resource", - resource: { - uri: "", - mimeType: "text/plain", - text: "Embedded resource content for testing." + messages: [ + { + role: 'user', + content: { + type: 'resource', + resource: { + uri: '', + mimeType: 'text/plain', + text: 'Embedded resource content for testing.' + } + } + }, + { + role: 'user', + content: { + type: 'text', + text: 'Please process the embedded resource above.' + } } - } - }, - { - role: "user", - content: { - type: "text", - text: "Please process the embedded resource above." - } - } - ] + ]; } ``` @@ -655,25 +700,26 @@ Implement these prompts with exact names: **Arguments**: None **Returns**: + ```typescript { - messages: [ - { - role: "user", - content: { - type: "image", - data: "", - mimeType: "image/png" - } - }, - { - role: "user", - content: { - type: "text", - text: "Please analyze the image above." - } - } - ] + messages: [ + { + role: 'user', + content: { + type: 'image', + data: '', + mimeType: 'image/png' + } + }, + { + role: 'user', + content: { + type: 'text', + text: 'Please analyze the image above.' + } + } + ]; } ``` @@ -686,11 +732,13 @@ Implement these prompts with exact names: **Endpoint**: `logging/setLevel` **Requirements**: + - Accept log level setting - Filter subsequent log notifications based on level - Return empty object `{}` **Log Levels** (in order of severity): + - `debug` - `info` - `notice` @@ -705,13 +753,15 @@ Implement these prompts with exact names: **Notification**: `notifications/message` **Requirements**: + - Send log notifications during operations - Each log MUST have: - - `level` (string) - one of the log levels above - - `data` (any) - log message or structured data - - `logger` (string, optional) - logger name + - `level` (string) - one of the log levels above + - `data` (any) - log message or structured data + - `logger` (string, optional) - logger name **Example**: + ```typescript { method: "notifications/message", @@ -726,12 +776,14 @@ Implement these prompts with exact names: **Implementation Note**: When no client is connected, log notifications may fail. Handle this gracefully by catching promise rejections: ```typescript -mcpServer.server.notification({ - method: "notifications/message", - params: { level, logger, data } -}).catch(() => { - // Ignore error if no client is connected -}); +mcpServer.server + .notification({ + method: 'notifications/message', + params: { level, logger, data } + }) + .catch(() => { + // Ignore error if no client is connected + }); ``` --- @@ -743,6 +795,7 @@ mcpServer.server.notification({ **Purpose**: Allow servers to return large result sets in manageable chunks. **Applies To**: + - `tools/list` - `resources/list` - `resources/templates/list` @@ -751,6 +804,7 @@ mcpServer.server.notification({ **Requirements**: **Request Format**: + ```typescript { "method": "tools/list", // or resources/list, prompts/list, etc. @@ -761,6 +815,7 @@ mcpServer.server.notification({ ``` **Response Format**: + ```typescript { "tools": [ /* array of tools */ ], // or resources, prompts, etc. @@ -769,6 +824,7 @@ mcpServer.server.notification({ ``` **Implementation Requirements**: + - If `cursor` parameter is provided, return results starting after that position - If more results are available, include `nextCursor` in response - Cursor tokens MUST be opaque strings (format is server-defined) @@ -776,6 +832,7 @@ mcpServer.server.notification({ - For conformance testing, pagination is optional (all items can be returned in single response) **Example**: + ```typescript // First request (no cursor) Request: { "method": "tools/list" } @@ -808,11 +865,13 @@ Response: { **Endpoint**: `completion/complete` **Requirements**: + - Accept completion requests for prompt or resource template arguments - Provide contextual suggestions based on partial input - Return array of completion values ranked by relevance **Request Format**: + ```typescript { "method": "completion/complete", @@ -830,6 +889,7 @@ Response: { ``` **Response Format**: + ```typescript { "completion": { @@ -856,6 +916,7 @@ npm start ``` Server should output: + ``` MCP Conformance Test Server running on http://localhost:3000 - MCP endpoint: http://localhost:3000/mcp @@ -870,5 +931,3 @@ npm run test:server -- --server-url http://localhost:3000/mcp --scenario server- # All tests npm run test:server -- --server-url http://localhost:3000/mcp --all ``` - - diff --git a/examples/servers/typescript/README.md b/examples/servers/typescript/README.md index 1bab7e0..ba5963f 100644 --- a/examples/servers/typescript/README.md +++ b/examples/servers/typescript/README.md @@ -7,6 +7,7 @@ A reference implementation of an MCP server that implements all features require This server implements: ### Tools + - `test_simple_text` - Returns simple text content - `test_image_content` - Returns image content (base64 PNG) - `test_audio_content` - Returns audio content (base64 WAV) @@ -20,18 +21,21 @@ This server implements: - `test_dynamic_tool` - Dynamically added/removed tool ### Resources + - `test://static-text` - Static text resource - `test://static-binary` - Static binary resource (image) - `test://template/{id}/data` - Resource template with parameter - `test://watched-resource` - Subscribable resource with updates ### Prompts + - `test_simple_prompt` - Simple prompt without arguments - `test_prompt_with_arguments(arg1, arg2)` - Parameterized prompt - `test_prompt_with_embedded_resource(resourceUri)` - Prompt with embedded resource - `test_prompt_with_image` - Prompt with image content ### Other Capabilities + - Logging at all levels (debug, info, notice, warning, error, critical, alert, emergency) - Completion support for prompt and resource arguments - List changed notifications for tools, resources, and prompts @@ -54,9 +58,11 @@ The server will start on `http://localhost:3000` (or the port specified in `PORT ## Endpoints ### MCP Endpoint + - `POST /mcp` - Main MCP protocol endpoint ### Health Check + - `GET /health` - Server health check ## Automatic Behaviors @@ -73,11 +79,13 @@ These behaviors allow testing of MCP notifications without requiring manual trig ## Example Usage ### Starting the Server + ```bash npm start ``` ### Testing with MCP Inspector + ```bash npx @modelcontextprotocol/inspector http://localhost:3000/mcp ``` @@ -85,6 +93,7 @@ npx @modelcontextprotocol/inspector http://localhost:3000/mcp ### Testing with curl #### Initialize + ```bash curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ @@ -104,6 +113,7 @@ curl -X POST http://localhost:3000/mcp \ ``` #### List Tools + ```bash curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ @@ -115,6 +125,7 @@ curl -X POST http://localhost:3000/mcp \ ``` #### Call Tool + ```bash curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ diff --git a/examples/servers/typescript/everything-server.ts b/examples/servers/typescript/everything-server.ts index fcac586..6fff9f4 100644 --- a/examples/servers/typescript/everything-server.ts +++ b/examples/servers/typescript/everything-server.ts @@ -55,32 +55,33 @@ function createMcpServer() { ); // Helper to send log messages using the underlying server - function sendLog(level: 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency', message: string, data?: any) { - mcpServer.server.notification({ - method: 'notifications/message', - params: { - level, - logger: 'conformance-test-server', - data: data || message - } - }).catch(() => { - // Ignore error if no client is connected - }); + function sendLog( + level: 'debug' | 'info' | 'notice' | 'warning' | 'error' | 'critical' | 'alert' | 'emergency', + message: string, + data?: any + ) { + mcpServer.server + .notification({ + method: 'notifications/message', + params: { + level, + logger: 'conformance-test-server', + data: data || message + } + }) + .catch(() => { + // Ignore error if no client is connected + }); } // ===== TOOLS ===== // Simple text tool - mcpServer.tool( - 'test_simple_text', - 'Tests simple text content response', - {}, - async () => { - return { - content: [{ type: 'text', text: 'This is a simple text response for testing.' }] - }; - } - ); + mcpServer.tool('test_simple_text', 'Tests simple text content response', {}, async () => { + return { + content: [{ type: 'text', text: 'This is a simple text response for testing.' }] + }; + }); // Image content tool mcpServer.registerTool( @@ -116,14 +117,16 @@ function createMcpServer() { }, async () => { return { - content: [{ - type: 'resource', - resource: { - uri: 'test://embedded-resource', - mimeType: 'text/plain', - text: 'This is an embedded resource content.' + content: [ + { + type: 'resource', + resource: { + uri: 'test://embedded-resource', + mimeType: 'text/plain', + text: 'This is an embedded resource content.' + } } - }] + ] }; } ); @@ -157,7 +160,7 @@ function createMcpServer() { 'test_tool_with_logging', { description: 'Tests tool that emits log messages during execution', - inputSchema: {} // Empty schema so callback gets (args, extra) instead of just (extra) + inputSchema: {} // Empty schema so callback gets (args, extra) instead of just (extra) }, async (_args, { sendNotification }) => { await sendNotification({ @@ -191,17 +194,16 @@ function createMcpServer() { } ); - // Tool with progress - registerTool with empty inputSchema to get (args, extra) signature mcpServer.registerTool( 'test_tool_with_progress', { description: 'Tests tool that reports progress notifications', - inputSchema: {} // Empty schema so callback gets (args, extra) instead of just (extra) + inputSchema: {} // Empty schema so callback gets (args, extra) instead of just (extra) }, async (_args, { sendNotification, _meta }) => { const progressToken = _meta?.progressToken ?? 0; - console.log("???? Progress token:", progressToken); + console.log('???? Progress token:', progressToken); await sendNotification({ method: 'notifications/progress', params: { @@ -279,24 +281,28 @@ function createMcpServer() { maxTokens: 100 } }, - (z.object({ method: z.literal('sampling/createMessage') }).passthrough() as any) + z.object({ method: z.literal('sampling/createMessage') }).passthrough() as any ); const samplingResult = result as any; const modelResponse = samplingResult.content?.text || samplingResult.message?.content?.text || 'No response'; return { - content: [{ - type: 'text', - text: `LLM response: ${modelResponse}` - }] + content: [ + { + type: 'text', + text: `LLM response: ${modelResponse}` + } + ] }; } catch (error: any) { return { - content: [{ - type: 'text', - text: `Sampling not supported or error: ${error.message}` - }] + content: [ + { + type: 'text', + text: `Sampling not supported or error: ${error.message}` + } + ] }; } } @@ -324,29 +330,33 @@ function createMcpServer() { properties: { response: { type: 'string', - description: 'User\'s response' + description: "User's response" } }, required: ['response'] } } }, - (z.object({ method: z.literal('elicitation/create') }).passthrough() as any) + z.object({ method: z.literal('elicitation/create') }).passthrough() as any ); const elicitResult = result as any; return { - content: [{ - type: 'text', - text: `User response: action=${elicitResult.action}, content=${JSON.stringify(elicitResult.content || {})}` - }] + content: [ + { + type: 'text', + text: `User response: action=${elicitResult.action}, content=${JSON.stringify(elicitResult.content || {})}` + } + ] }; } catch (error: any) { return { - content: [{ - type: 'text', - text: `Elicitation not supported or error: ${error.message}` - }] + content: [ + { + type: 'text', + text: `Elicitation not supported or error: ${error.message}` + } + ] }; } } @@ -367,11 +377,13 @@ function createMcpServer() { }, async () => { return { - contents: [{ - uri: 'test://static-text', - mimeType: 'text/plain', - text: 'This is the content of the static text resource.' - }] + contents: [ + { + uri: 'test://static-text', + mimeType: 'text/plain', + text: 'This is the content of the static text resource.' + } + ] }; } ); @@ -387,11 +399,13 @@ function createMcpServer() { }, async () => { return { - contents: [{ - uri: 'test://static-binary', - mimeType: 'image/png', - blob: TEST_IMAGE_BASE64 - }] + contents: [ + { + uri: 'test://static-binary', + mimeType: 'image/png', + blob: TEST_IMAGE_BASE64 + } + ] }; } ); @@ -410,15 +424,17 @@ function createMcpServer() { async (uri, variables) => { const id = variables.id; return { - contents: [{ - uri: uri.toString(), - mimeType: 'application/json', - text: JSON.stringify({ - id, - templateTest: true, - data: `Data for ID: ${id}` - }) - }] + contents: [ + { + uri: uri.toString(), + mimeType: 'application/json', + text: JSON.stringify({ + id, + templateTest: true, + data: `Data for ID: ${id}` + }) + } + ] }; } ); @@ -434,35 +450,31 @@ function createMcpServer() { }, async () => { return { - contents: [{ - uri: 'test://watched-resource', - mimeType: 'text/plain', - text: watchedResourceContent - }] + contents: [ + { + uri: 'test://watched-resource', + mimeType: 'text/plain', + text: watchedResourceContent + } + ] }; } ); // Subscribe/Unsubscribe handlers - mcpServer.server.setRequestHandler( - z.object({ method: z.literal('resources/subscribe') }).passthrough(), - async (request: any) => { - const uri = request.params.uri; - resourceSubscriptions.add(uri); - sendLog('info', `Subscribed to resource: ${uri}`); - return {}; - } - ); - - mcpServer.server.setRequestHandler( - z.object({ method: z.literal('resources/unsubscribe') }).passthrough(), - async (request: any) => { - const uri = request.params.uri; - resourceSubscriptions.delete(uri); - sendLog('info', `Unsubscribed from resource: ${uri}`); - return {}; - } - ); + mcpServer.server.setRequestHandler(z.object({ method: z.literal('resources/subscribe') }).passthrough(), async (request: any) => { + const uri = request.params.uri; + resourceSubscriptions.add(uri); + sendLog('info', `Subscribed to resource: ${uri}`); + return {}; + }); + + mcpServer.server.setRequestHandler(z.object({ method: z.literal('resources/unsubscribe') }).passthrough(), async (request: any) => { + const uri = request.params.uri; + resourceSubscriptions.delete(uri); + sendLog('info', `Unsubscribed from resource: ${uri}`); + return {}; + }); // ===== PROMPTS ===== @@ -475,10 +487,12 @@ function createMcpServer() { }, async () => { return { - messages: [{ - role: 'user', - content: { type: 'text', text: 'This is a simple prompt for testing.' } - }] + messages: [ + { + role: 'user', + content: { type: 'text', text: 'This is a simple prompt for testing.' } + } + ] }; } ); @@ -494,16 +508,18 @@ function createMcpServer() { arg2: z.string().describe('Second test argument') } }, - async (args) => { + async args => { const { arg1, arg2 } = args; return { - messages: [{ - role: 'user', - content: { - type: 'text', - text: `Prompt with arguments: arg1='${arg1}', arg2='${arg2}'` + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Prompt with arguments: arg1='${arg1}', arg2='${arg2}'` + } } - }] + ] }; } ); @@ -518,7 +534,7 @@ function createMcpServer() { resourceUri: z.string().describe('URI of the resource to embed') } }, - async (args) => { + async args => { const uri = args.resourceUri; return { messages: [ @@ -567,31 +583,25 @@ function createMcpServer() { // ===== LOGGING ===== - mcpServer.server.setRequestHandler( - z.object({ method: z.literal('logging/setLevel') }).passthrough(), - async (request: any) => { - const level = request.params.level; - sendLog('info', `Log level set to: ${level}`); - return {}; - } - ); + mcpServer.server.setRequestHandler(z.object({ method: z.literal('logging/setLevel') }).passthrough(), async (request: any) => { + const level = request.params.level; + sendLog('info', `Log level set to: ${level}`); + return {}; + }); // ===== COMPLETION ===== - mcpServer.server.setRequestHandler( - z.object({ method: z.literal('completion/complete') }).passthrough(), - async (_request: any) => { - // Basic completion support - returns empty array for conformance - // Real implementations would provide contextual suggestions - return { - completion: { - values: [], - total: 0, - hasMore: false - } - }; - } - ); + mcpServer.server.setRequestHandler(z.object({ method: z.literal('completion/complete') }).passthrough(), async (_request: any) => { + // Basic completion support - returns empty array for conformance + // Real implementations would provide contextual suggestions + return { + completion: { + values: [], + total: 0, + hasMore: false + } + }; + }); return mcpServer; } @@ -607,11 +617,13 @@ const app = express(); app.use(express.json()); // Configure CORS to expose Mcp-Session-Id header for browser-based clients -app.use(cors({ - origin: '*', // Allow all origins - exposedHeaders: ['Mcp-Session-Id'], - allowedHeaders: ['Content-Type', 'mcp-session-id', 'last-event-id'] -})); +app.use( + cors({ + origin: '*', // Allow all origins + exposedHeaders: ['Mcp-Session-Id'], + allowedHeaders: ['Content-Type', 'mcp-session-id', 'last-event-id'] + }) +); // Handle POST requests - stateful mode app.post('/mcp', async (req, res) => { @@ -629,7 +641,7 @@ app.post('/mcp', async (req, res) => { transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), - onsessioninitialized: (newSessionId) => { + onsessioninitialized: newSessionId => { transports[newSessionId] = transport; servers[newSessionId] = mcpServer; console.log(`Session initialized with ID: ${newSessionId}`); diff --git a/examples/servers/typescript/tsconfig.json b/examples/servers/typescript/tsconfig.json index 89fe38c..5363227 100644 --- a/examples/servers/typescript/tsconfig.json +++ b/examples/servers/typescript/tsconfig.json @@ -4,7 +4,5 @@ "outDir": "./dist", "rootDir": "." }, - "include": [ - "*.ts" - ] + "include": ["*.ts"] } diff --git a/src/checks/index.ts b/src/checks/index.ts index b262c17..cb807fe 100644 --- a/src/checks/index.ts +++ b/src/checks/index.ts @@ -1,6 +1,4 @@ -// Namespaced exports for client and server checks +// Namespaced exports for client checks import * as client from './client.js'; -import * as server from './server.js'; export const clientChecks = client; -export const serverChecks = server; diff --git a/src/checks/server.ts b/src/checks/server.ts index df82fc0..e69de29 100644 --- a/src/checks/server.ts +++ b/src/checks/server.ts @@ -1 +0,0 @@ -import { ConformanceCheck, CheckStatus } from '../types.js'; diff --git a/src/scenarios/client/tools_call.ts b/src/scenarios/client/tools_call.ts index 6ec2f3b..0310a26 100644 --- a/src/scenarios/client/tools_call.ts +++ b/src/scenarios/client/tools_call.ts @@ -2,6 +2,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import type { Scenario, ConformanceCheck } from '../../types.js'; +// @ts-expect-error - express has incomplete type definitions import express, { Request, Response, NextFunction } from 'express'; import { ScenarioUrls } from '../../types.js'; diff --git a/src/scenarios/index.ts b/src/scenarios/index.ts index e5fd364..fd69fc2 100644 --- a/src/scenarios/index.ts +++ b/src/scenarios/index.ts @@ -3,14 +3,9 @@ import { InitializeScenario } from './client/initialize.js'; import { ToolsCallScenario } from './client/tools_call.js'; // Import all new server test scenarios -import { - ServerInitializeScenario -} from './server/lifecycle.js'; +import { ServerInitializeScenario } from './server/lifecycle.js'; -import { - LoggingSetLevelScenario, - CompletionCompleteScenario -} from './server/utils.js'; +import { LoggingSetLevelScenario, CompletionCompleteScenario } from './server/utils.js'; import { ToolsListScenario, diff --git a/src/scenarios/server/client-helper.ts b/src/scenarios/server/client-helper.ts index 08f023a..27fed86 100644 --- a/src/scenarios/server/client-helper.ts +++ b/src/scenarios/server/client-helper.ts @@ -50,20 +50,14 @@ export class NotificationCollector { constructor(client: Client) { // Set up notification handler for logging messages - client.setNotificationHandler( - LoggingMessageNotificationSchema, - (notification) => { - this.loggingNotifications.push(notification); - } - ); + client.setNotificationHandler(LoggingMessageNotificationSchema, notification => { + this.loggingNotifications.push(notification); + }); // Set up notification handler for progress notifications - client.setNotificationHandler( - ProgressNotificationSchema, - (notification) => { - this.progressNotifications.push(notification); - } - ); + client.setNotificationHandler(ProgressNotificationSchema, notification => { + this.progressNotifications.push(notification); + }); } /** diff --git a/src/scenarios/server/lifecycle.ts b/src/scenarios/server/lifecycle.ts index 8a82cc7..f525c09 100644 --- a/src/scenarios/server/lifecycle.ts +++ b/src/scenarios/server/lifecycle.ts @@ -23,10 +23,12 @@ export class ServerInitializeScenario implements ClientScenario { description: 'Server responds to initialize request with valid structure', status: 'SUCCESS', timestamp: new Date().toISOString(), - specReferences: [{ - id: 'MCP-Initialize', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization' - }], + specReferences: [ + { + id: 'MCP-Initialize', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization' + } + ], details: { serverUrl, connected: true @@ -42,10 +44,12 @@ export class ServerInitializeScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed to initialize: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Initialize', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization' - }] + specReferences: [ + { + id: 'MCP-Initialize', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization' + } + ] }); } diff --git a/src/scenarios/server/prompts.ts b/src/scenarios/server/prompts.ts index ce4eace..563933b 100644 --- a/src/scenarios/server/prompts.ts +++ b/src/scenarios/server/prompts.ts @@ -39,10 +39,12 @@ export class PromptsListScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Prompts-List', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts' - }], + specReferences: [ + { + id: 'MCP-Prompts-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts' + } + ], details: { promptCount: result.prompts?.length || 0, prompts: result.prompts?.map(p => p.name) @@ -58,10 +60,12 @@ export class PromptsListScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Prompts-List', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts' - }] + specReferences: [ + { + id: 'MCP-Prompts-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts' + } + ] }); } @@ -101,10 +105,12 @@ export class PromptsGetSimpleScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Prompts-Get', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' - }], + specReferences: [ + { + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + } + ], details: { messageCount: result.messages?.length || 0 } @@ -119,10 +125,12 @@ export class PromptsGetSimpleScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Prompts-Get', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' - }] + specReferences: [ + { + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + } + ] }); } @@ -169,10 +177,12 @@ export class PromptsGetWithArgsScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Prompts-Get', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' - }], + specReferences: [ + { + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + } + ], details: { messageCount: result.messages?.length || 0, messages: result.messages @@ -188,10 +198,12 @@ export class PromptsGetWithArgsScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Prompts-Get', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' - }] + specReferences: [ + { + id: 'MCP-Prompts-Get', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts' + } + ] }); } @@ -221,9 +233,8 @@ export class PromptsGetEmbeddedResourceScenario implements ClientScenario { if (!result.messages) errors.push('Missing messages array'); // Look for resource content - const hasResourceContent = result.messages.some((msg: any) => - msg.content?.type === 'resource' || - (msg.content?.resource !== undefined) + const hasResourceContent = result.messages.some( + (msg: any) => msg.content?.type === 'resource' || msg.content?.resource !== undefined ); if (!hasResourceContent) { @@ -237,10 +248,12 @@ export class PromptsGetEmbeddedResourceScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Prompts-Embedded-Resources', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources' - }], + specReferences: [ + { + id: 'MCP-Prompts-Embedded-Resources', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources' + } + ], details: { messageCount: result.messages?.length || 0, messages: result.messages @@ -256,10 +269,12 @@ export class PromptsGetEmbeddedResourceScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Prompts-Embedded-Resources', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources' - }] + specReferences: [ + { + id: 'MCP-Prompts-Embedded-Resources', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources' + } + ] }); } @@ -286,10 +301,8 @@ export class PromptsGetWithImageScenario implements ClientScenario { if (!result.messages) errors.push('Missing messages array'); // Look for image content - const hasImageContent = result.messages.some((msg: any) => - msg.content?.type === 'image' && - msg.content?.data && - msg.content?.mimeType + const hasImageContent = result.messages.some( + (msg: any) => msg.content?.type === 'image' && msg.content?.data && msg.content?.mimeType ); if (!hasImageContent) { @@ -303,10 +316,12 @@ export class PromptsGetWithImageScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Prompts-Image', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content' - }], + specReferences: [ + { + id: 'MCP-Prompts-Image', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content' + } + ], details: { messageCount: result.messages?.length || 0 } @@ -321,10 +336,12 @@ export class PromptsGetWithImageScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Prompts-Image', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content' - }] + specReferences: [ + { + id: 'MCP-Prompts-Image', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content' + } + ] }); } diff --git a/src/scenarios/server/resources.ts b/src/scenarios/server/resources.ts index e1a9464..f475e55 100644 --- a/src/scenarios/server/resources.ts +++ b/src/scenarios/server/resources.ts @@ -39,10 +39,12 @@ export class ResourcesListScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Resources-List', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources' - }], + specReferences: [ + { + id: 'MCP-Resources-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources' + } + ], details: { resourceCount: result.resources?.length || 0, resources: result.resources?.map(r => r.uri) @@ -58,10 +60,12 @@ export class ResourcesListScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Resources-List', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources' - }] + specReferences: [ + { + id: 'MCP-Resources-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources' + } + ] }); } @@ -103,10 +107,12 @@ export class ResourcesReadTextScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Resources-Read', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' - }], + specReferences: [ + { + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + } + ], details: { uri: content?.uri, mimeType: content?.mimeType, @@ -123,10 +129,12 @@ export class ResourcesReadTextScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Resources-Read', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' - }] + specReferences: [ + { + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + } + ] }); } @@ -167,10 +175,12 @@ export class ResourcesReadBinaryScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Resources-Read', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' - }], + specReferences: [ + { + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + } + ], details: { uri: content?.uri, mimeType: content?.mimeType, @@ -187,10 +197,12 @@ export class ResourcesReadBinaryScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Resources-Read', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' - }] + specReferences: [ + { + id: 'MCP-Resources-Read', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources' + } + ] }); } @@ -236,10 +248,12 @@ export class ResourcesTemplateReadScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Resources-Templates', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates' - }], + specReferences: [ + { + id: 'MCP-Resources-Templates', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates' + } + ], details: { uri: content?.uri, content: content?.text || content?.blob @@ -255,10 +269,12 @@ export class ResourcesTemplateReadScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Resources-Templates', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates' - }] + specReferences: [ + { + id: 'MCP-Resources-Templates', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates' + } + ] }); } @@ -286,10 +302,12 @@ export class ResourcesSubscribeScenario implements ClientScenario { description: 'Subscribe to resource successfully', status: 'SUCCESS', timestamp: new Date().toISOString(), - specReferences: [{ - id: 'MCP-Resources-Subscribe', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' - }] + specReferences: [ + { + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + } + ] }); await connection.close(); @@ -301,10 +319,12 @@ export class ResourcesSubscribeScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Resources-Subscribe', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' - }] + specReferences: [ + { + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + } + ] }); } @@ -338,10 +358,12 @@ export class ResourcesUnsubscribeScenario implements ClientScenario { description: 'Unsubscribe from resource successfully', status: 'SUCCESS', timestamp: new Date().toISOString(), - specReferences: [{ - id: 'MCP-Resources-Subscribe', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' - }] + specReferences: [ + { + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + } + ] }); await connection.close(); @@ -353,10 +375,12 @@ export class ResourcesUnsubscribeScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Resources-Subscribe', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' - }] + specReferences: [ + { + id: 'MCP-Resources-Subscribe', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions' + } + ] }); } diff --git a/src/scenarios/server/tools.ts b/src/scenarios/server/tools.ts index da0aef2..21a2302 100644 --- a/src/scenarios/server/tools.ts +++ b/src/scenarios/server/tools.ts @@ -4,15 +4,7 @@ import { ClientScenario, ConformanceCheck } from '../../types.js'; import { connectToServer, NotificationCollector } from './client-helper.js'; -import { - ListToolsResultSchema, - CallToolResultSchema, - LoggingMessageNotificationSchema, - ProgressNotificationSchema, - CreateMessageRequestSchema, - ElicitRequestSchema, - Progress -} from '@modelcontextprotocol/sdk/types.js'; +import { CallToolResultSchema, CreateMessageRequestSchema, ElicitRequestSchema, Progress } from '@modelcontextprotocol/sdk/types.js'; export class ToolsListScenario implements ClientScenario { name = 'tools-list'; @@ -49,10 +41,12 @@ export class ToolsListScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Tools-List', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools' - }], + specReferences: [ + { + id: 'MCP-Tools-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools' + } + ], details: { toolCount: result.tools?.length || 0, tools: result.tools?.map(t => t.name) @@ -68,10 +62,12 @@ export class ToolsListScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Tools-List', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools' - }] + specReferences: [ + { + id: 'MCP-Tools-List', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools' + } + ] }); } @@ -112,10 +108,12 @@ export class ToolsCallSimpleTextScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }], + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ], details: { result } @@ -130,10 +128,12 @@ export class ToolsCallSimpleTextScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }] + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ] }); } @@ -173,10 +173,12 @@ export class ToolsCallImageScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }], + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ], details: { mimeType: imageContent?.mimeType, hasData: !!imageContent?.data @@ -192,10 +194,12 @@ export class ToolsCallImageScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }] + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ] }); } @@ -239,10 +243,12 @@ export class ToolsCallMultipleContentTypesScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }], + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ], details: { contentCount: content ? content.length : 0, contentTypes: content ? content.map((c: any) => c.type) : [] @@ -258,10 +264,12 @@ export class ToolsCallMultipleContentTypesScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }] + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ] }); } @@ -280,7 +288,7 @@ export class ToolsCallWithLoggingScenario implements ClientScenario { const connection = await connectToServer(serverUrl); const notifications = new NotificationCollector(connection.client); - const result = await connection.client.callTool({ + await connection.client.callTool({ name: 'test_tool_with_logging', arguments: {} }); @@ -305,10 +313,12 @@ export class ToolsCallWithLoggingScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Logging', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' - }], + specReferences: [ + { + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + } + ], details: { logCount: logNotifications.length, logs: logNotifications.map((n: any) => n.params) @@ -324,10 +334,12 @@ export class ToolsCallWithLoggingScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Logging', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' - }] + specReferences: [ + { + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + } + ] }); } @@ -359,14 +371,19 @@ export class ToolsCallErrorScenario implements ClientScenario { id: 'tools-call-error', name: 'ToolsCallError', description: 'Tool returns error correctly', - status: (hasIsError && hasErrorMessage) ? 'SUCCESS' : 'FAILURE', + status: hasIsError && hasErrorMessage ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), - errorMessage: !hasIsError ? 'Tool did not return isError: true' : - !hasErrorMessage ? 'Error result missing error message' : undefined, - specReferences: [{ - id: 'MCP-Error-Handling', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' - }], + errorMessage: !hasIsError + ? 'Tool did not return isError: true' + : !hasErrorMessage + ? 'Error result missing error message' + : undefined, + specReferences: [ + { + id: 'MCP-Error-Handling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' + } + ], details: { result } @@ -381,10 +398,12 @@ export class ToolsCallErrorScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Error-Handling', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' - }] + specReferences: [ + { + id: 'MCP-Error-Handling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle' + } + ] }); } @@ -446,10 +465,12 @@ export class ToolsCallWithProgressScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Progress', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress' - }], + specReferences: [ + { + id: 'MCP-Progress', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress' + } + ], details: { progressCount: progressUpdates.length, progressNotifications: progressUpdates.map((n: Progress) => n), @@ -466,10 +487,12 @@ export class ToolsCallWithProgressScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Progress', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress' - }] + specReferences: [ + { + id: 'MCP-Progress', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress' + } + ] }); } @@ -488,21 +511,18 @@ export class ToolsCallSamplingScenario implements ClientScenario { const connection = await connectToServer(serverUrl); let samplingRequested = false; - connection.client.setRequestHandler( - CreateMessageRequestSchema, - async (_request) => { - samplingRequested = true; - return { - role: 'assistant', - content: { - type: 'text', - text: 'This is a test response from the client' - }, - model: 'test-model', - stopReason: 'endTurn' - }; - } - ); + connection.client.setRequestHandler(CreateMessageRequestSchema, async _request => { + samplingRequested = true; + return { + role: 'assistant', + content: { + type: 'text', + text: 'This is a test response from the client' + }, + model: 'test-model', + stopReason: 'endTurn' + }; + }); const result = await connection.client.callTool({ name: 'test_sampling', @@ -528,10 +548,12 @@ export class ToolsCallSamplingScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Sampling', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling' - }], + specReferences: [ + { + id: 'MCP-Sampling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling' + } + ], details: { samplingRequested, result @@ -547,10 +569,12 @@ export class ToolsCallSamplingScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Sampling', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling' - }] + specReferences: [ + { + id: 'MCP-Sampling', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling' + } + ] }); } @@ -569,19 +593,16 @@ export class ToolsCallElicitationScenario implements ClientScenario { const connection = await connectToServer(serverUrl); let elicitationRequested = false; - connection.client.setRequestHandler( - ElicitRequestSchema, - async (_request) => { - elicitationRequested = true; - return { - action: 'accept', - content: { - username: 'testuser', - email: 'test@example.com' - } - }; - } - ); + connection.client.setRequestHandler(ElicitRequestSchema, async _request => { + elicitationRequested = true; + return { + action: 'accept', + content: { + username: 'testuser', + email: 'test@example.com' + } + }; + }); const result = await connection.client.callTool({ name: 'test_elicitation', @@ -607,10 +628,12 @@ export class ToolsCallElicitationScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Elicitation', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation' - }], + specReferences: [ + { + id: 'MCP-Elicitation', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation' + } + ], details: { elicitationRequested, result @@ -626,10 +649,12 @@ export class ToolsCallElicitationScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Elicitation', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation' - }] + specReferences: [ + { + id: 'MCP-Elicitation', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation' + } + ] }); } @@ -663,7 +688,8 @@ export class ToolsCallAudioScenario implements ClientScenario { if (!audioContent) errors.push('No audio content found'); if (audioContent && !audioContent.data) errors.push('Audio content missing data field'); if (audioContent && !audioContent.mimeType) errors.push('Audio content missing mimeType field'); - if (audioContent && audioContent.mimeType !== 'audio/wav') errors.push(`Expected mimeType 'audio/wav', got '${audioContent.mimeType}'`); + if (audioContent && audioContent.mimeType !== 'audio/wav') + errors.push(`Expected mimeType 'audio/wav', got '${audioContent.mimeType}'`); checks.push({ id: 'tools-call-audio', @@ -672,10 +698,12 @@ export class ToolsCallAudioScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }], + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ], details: { hasAudioContent: !!audioContent, audioDataLength: audioContent?.data?.length || 0 @@ -691,10 +719,12 @@ export class ToolsCallAudioScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }] + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ] }); } @@ -742,10 +772,12 @@ export class ToolsCallEmbeddedResourceScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }], + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ], details: { hasResourceContent: !!resourceContent, resourceUri: resourceContent?.resource?.uri @@ -761,10 +793,12 @@ export class ToolsCallEmbeddedResourceScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Tools-Call', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' - }] + specReferences: [ + { + id: 'MCP-Tools-Call', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools' + } + ] }); } diff --git a/src/scenarios/server/utils.ts b/src/scenarios/server/utils.ts index f705bc6..511d4ac 100644 --- a/src/scenarios/server/utils.ts +++ b/src/scenarios/server/utils.ts @@ -31,10 +31,12 @@ export class LoggingSetLevelScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Logging', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' - }], + specReferences: [ + { + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + } + ], details: { result } @@ -49,10 +51,12 @@ export class LoggingSetLevelScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Logging', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' - }] + specReferences: [ + { + id: 'MCP-Logging', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging' + } + ] }); } @@ -71,18 +75,16 @@ export class CompletionCompleteScenario implements ClientScenario { const connection = await connectToServer(serverUrl); // Send completion/complete request - const result = await connection.client.complete( - { - ref: { - type: 'ref/prompt', - name: 'test_prompt_with_arguments' - }, - argument: { - name: 'arg1', - value: 'test' - } + const result = await connection.client.complete({ + ref: { + type: 'ref/prompt', + name: 'test_prompt_with_arguments' + }, + argument: { + name: 'arg1', + value: 'test' } - ); + }); // Validate response structure const errors: string[] = []; @@ -100,10 +102,12 @@ export class CompletionCompleteScenario implements ClientScenario { status: errors.length === 0 ? 'SUCCESS' : 'FAILURE', timestamp: new Date().toISOString(), errorMessage: errors.length > 0 ? errors.join('; ') : undefined, - specReferences: [{ - id: 'MCP-Completion', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion' - }], + specReferences: [ + { + id: 'MCP-Completion', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion' + } + ], details: { result } @@ -118,10 +122,12 @@ export class CompletionCompleteScenario implements ClientScenario { status: 'FAILURE', timestamp: new Date().toISOString(), errorMessage: `Failed: ${error instanceof Error ? error.message : String(error)}`, - specReferences: [{ - id: 'MCP-Completion', - url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion' - }] + specReferences: [ + { + id: 'MCP-Completion', + url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion' + } + ] }); } diff --git a/src/server-runner.ts b/src/server-runner.ts index c97fb89..bfd983d 100644 --- a/src/server-runner.ts +++ b/src/server-runner.ts @@ -75,7 +75,7 @@ async function main(): Promise { } try { - let allResults: { scenario: string; checks: ConformanceCheck[] }[] = []; + const allResults: { scenario: string; checks: ConformanceCheck[] }[] = []; if (runAll) { // Get all server scenarios @@ -93,14 +93,16 @@ async function main(): Promise { console.error(`Failed to run scenario ${scenarioName}:`, error); allResults.push({ scenario: scenarioName, - checks: [{ - id: scenarioName, - name: scenarioName, - description: `Failed to run scenario`, - status: 'FAILURE', - timestamp: new Date().toISOString(), - errorMessage: error instanceof Error ? error.message : String(error) - }] + checks: [ + { + id: scenarioName, + name: scenarioName, + description: `Failed to run scenario`, + status: 'FAILURE', + timestamp: new Date().toISOString(), + errorMessage: error instanceof Error ? error.message : String(error) + } + ] }); } } From 4510df6d0f9c5327593809c570591a09ea36e629 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Tue, 4 Nov 2025 15:06:51 +0000 Subject: [PATCH 5/5] fix package lock agian --- package-lock.json | 53 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5336baf..63b2b97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1790,7 +1789,7 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/body-parser/-/body-parser-1.19.6.tgz", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", @@ -1801,7 +1800,7 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/connect/-/connect-3.4.38.tgz", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", @@ -1817,21 +1816,21 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.3", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/express/-/express-5.0.3.tgz", - "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.7", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", - "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "dev": true, "license": "MIT", "dependencies": { @@ -1853,7 +1852,7 @@ }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/http-errors/-/http-errors-2.0.5.tgz", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "license": "MIT" @@ -1905,8 +1904,8 @@ }, "node_modules/@types/mime": { "version": "1.3.5", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/mime/1.3.5/mime-1.3.5.tgz", - "integrity": "sha1-HvMC4Bz30rWg+lJnkMkSO/HQZpA=", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true, "license": "MIT" }, @@ -1922,22 +1921,22 @@ }, "node_modules/@types/qs": { "version": "6.14.0", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/qs/-/qs-6.14.0.tgz", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/range-parser/-/range-parser-1.2.7.tgz", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { - "version": "1.2.0", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1945,9 +1944,9 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.9", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { @@ -1957,9 +1956,9 @@ } }, "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-all/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "license": "MIT", "dependencies": { @@ -2520,9 +2519,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.23", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", - "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "version": "2.8.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.24.tgz", + "integrity": "sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==", "dev": true, "license": "Apache-2.0", "bin": {