adjust prettier to use more defaults (#8)
* simplify prettier to use more defaults * apply new prettier
This commit is contained in:
parent
05df535e40
commit
b377251b15
10 changed files with 12352 additions and 12153 deletions
|
|
@ -3,133 +3,137 @@ import { Scenario, ScenarioUrls, ConformanceCheck } from '../../types.js';
|
|||
import { clientChecks } from '../../checks/index.js';
|
||||
|
||||
export class InitializeScenario implements Scenario {
|
||||
name = 'initialize';
|
||||
description = 'Tests MCP client initialization handshake';
|
||||
name = 'initialize';
|
||||
description = 'Tests MCP client initialization handshake';
|
||||
|
||||
private server: http.Server | null = null;
|
||||
private checks: ConformanceCheck[] = [];
|
||||
private port: number = 0;
|
||||
private server: http.Server | null = null;
|
||||
private checks: ConformanceCheck[] = [];
|
||||
private port: number = 0;
|
||||
|
||||
async start(): Promise<ScenarioUrls> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.server = http.createServer((req, res) => {
|
||||
this.handleRequest(req, res);
|
||||
});
|
||||
async start(): Promise<ScenarioUrls> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.server = http.createServer((req, res) => {
|
||||
this.handleRequest(req, res);
|
||||
});
|
||||
|
||||
this.server.on('error', reject);
|
||||
this.server.on('error', reject);
|
||||
|
||||
this.server.listen(0, () => {
|
||||
const address = this.server!.address();
|
||||
if (address && typeof address === 'object') {
|
||||
this.port = address.port;
|
||||
resolve({
|
||||
serverUrl: `http://localhost:${this.port}`
|
||||
});
|
||||
} else {
|
||||
reject(new Error('Failed to get server address'));
|
||||
}
|
||||
});
|
||||
this.server.listen(0, () => {
|
||||
const address = this.server!.address();
|
||||
if (address && typeof address === 'object') {
|
||||
this.port = address.port;
|
||||
resolve({
|
||||
serverUrl: `http://localhost:${this.port}`
|
||||
});
|
||||
} else {
|
||||
reject(new Error('Failed to get server address'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.server) {
|
||||
this.server.close((err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
this.server = null;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.server) {
|
||||
this.server.close(err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
this.server = null;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
getChecks(): ConformanceCheck[] {
|
||||
return this.checks;
|
||||
}
|
||||
|
||||
getChecks(): ConformanceCheck[] {
|
||||
return this.checks;
|
||||
}
|
||||
private handleRequest(
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
): void {
|
||||
let body = '';
|
||||
|
||||
private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
let body = '';
|
||||
req.on('data', (chunk) => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const request = JSON.parse(body);
|
||||
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const request = JSON.parse(body);
|
||||
|
||||
if (request.method === 'initialize') {
|
||||
this.handleInitialize(request, res);
|
||||
} else if (request.method === 'tools/list') {
|
||||
this.handleToolsList(request, res);
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: request.id,
|
||||
result: {}
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: -32700,
|
||||
message: `Parse error ${error}`
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleInitialize(request: any, res: http.ServerResponse): void {
|
||||
const initializeRequest = request.params;
|
||||
|
||||
const check = clientChecks.createClientInitializationCheck(initializeRequest);
|
||||
this.checks.push(check);
|
||||
|
||||
const serverInfo = {
|
||||
name: 'test-server',
|
||||
version: '1.0.0'
|
||||
};
|
||||
|
||||
this.checks.push(clientChecks.createServerInfoCheck(serverInfo));
|
||||
|
||||
const response = {
|
||||
if (request.method === 'initialize') {
|
||||
this.handleInitialize(request, res);
|
||||
} else if (request.method === 'tools/list') {
|
||||
this.handleToolsList(request, res);
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: request.id,
|
||||
result: {}
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: request.id,
|
||||
result: {
|
||||
protocolVersion: '2025-06-18',
|
||||
serverInfo,
|
||||
capabilities: {}
|
||||
error: {
|
||||
code: -32700,
|
||||
message: `Parse error ${error}`
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(response));
|
||||
}
|
||||
private handleInitialize(request: any, res: http.ServerResponse): void {
|
||||
const initializeRequest = request.params;
|
||||
|
||||
private handleToolsList(request: any, res: http.ServerResponse): void {
|
||||
const response = {
|
||||
jsonrpc: '2.0',
|
||||
id: request.id,
|
||||
result: {
|
||||
tools: []
|
||||
}
|
||||
};
|
||||
const check =
|
||||
clientChecks.createClientInitializationCheck(initializeRequest);
|
||||
this.checks.push(check);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(response));
|
||||
}
|
||||
const serverInfo = {
|
||||
name: 'test-server',
|
||||
version: '1.0.0'
|
||||
};
|
||||
|
||||
this.checks.push(clientChecks.createServerInfoCheck(serverInfo));
|
||||
|
||||
const response = {
|
||||
jsonrpc: '2.0',
|
||||
id: request.id,
|
||||
result: {
|
||||
protocolVersion: '2025-06-18',
|
||||
serverInfo,
|
||||
capabilities: {}
|
||||
}
|
||||
};
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(response));
|
||||
}
|
||||
|
||||
private handleToolsList(request: any, res: http.ServerResponse): void {
|
||||
const response = {
|
||||
jsonrpc: '2.0',
|
||||
id: request.id,
|
||||
result: {
|
||||
tools: []
|
||||
}
|
||||
};
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(response));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,174 +1,177 @@
|
|||
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 {
|
||||
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';
|
||||
|
||||
function createServer(checks: ConformanceCheck[]): express.Application {
|
||||
const server = new Server(
|
||||
const server = new Server(
|
||||
{
|
||||
name: 'add-numbers-server',
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: 'add-numbers-server',
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {}
|
||||
}
|
||||
name: 'add_numbers',
|
||||
description: 'Add two numbers together',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: {
|
||||
type: 'number',
|
||||
description: 'First number'
|
||||
},
|
||||
b: {
|
||||
type: 'number',
|
||||
description: 'Second number'
|
||||
}
|
||||
},
|
||||
required: ['a', 'b']
|
||||
}
|
||||
}
|
||||
);
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: 'add_numbers',
|
||||
description: 'Add two numbers together',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: {
|
||||
type: 'number',
|
||||
description: 'First number'
|
||||
},
|
||||
b: {
|
||||
type: 'number',
|
||||
description: 'Second number'
|
||||
}
|
||||
},
|
||||
required: ['a', 'b']
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === 'add_numbers') {
|
||||
const { a, b } = request.params.arguments as { a: number; b: number };
|
||||
const result = a + b;
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async request => {
|
||||
if (request.params.name === 'add_numbers') {
|
||||
const { a, b } = request.params.arguments as { a: number; b: number };
|
||||
const result = a + b;
|
||||
|
||||
checks.push({
|
||||
id: 'tool-add-numbers',
|
||||
name: 'ToolAddNumbers',
|
||||
description: 'Validates that the add_numbers tool works correctly',
|
||||
status: 'SUCCESS',
|
||||
timestamp: new Date().toISOString(),
|
||||
specReferences: [
|
||||
{
|
||||
id: 'MCP-Tools',
|
||||
url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools'
|
||||
}
|
||||
],
|
||||
details: {
|
||||
a,
|
||||
b,
|
||||
result
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `The sum of ${a} and ${b} is ${result}`
|
||||
}
|
||||
]
|
||||
};
|
||||
checks.push({
|
||||
id: 'tool-add-numbers',
|
||||
name: 'ToolAddNumbers',
|
||||
description: 'Validates that the add_numbers tool works correctly',
|
||||
status: 'SUCCESS',
|
||||
timestamp: new Date().toISOString(),
|
||||
specReferences: [
|
||||
{
|
||||
id: 'MCP-Tools',
|
||||
url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools'
|
||||
}
|
||||
],
|
||||
details: {
|
||||
a,
|
||||
b,
|
||||
result
|
||||
}
|
||||
});
|
||||
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `The sum of ${a} and ${b} is ${result}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
});
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
// Log incoming requests for debugging
|
||||
// console.log(`Incoming request: ${req.method} ${req.url}`);
|
||||
checks.push({
|
||||
id: 'incoming-request',
|
||||
name: 'IncomingRequest',
|
||||
description: `Received ${req.method} request for ${req.url}`,
|
||||
status: 'INFO',
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
body: JSON.stringify(req.body)
|
||||
}
|
||||
});
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
// Log incoming requests for debugging
|
||||
// console.log(`Incoming request: ${req.method} ${req.url}`);
|
||||
checks.push({
|
||||
id: 'incoming-request',
|
||||
name: 'IncomingRequest',
|
||||
description: `Received ${req.method} request for ${req.url}`,
|
||||
status: 'INFO',
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
body: JSON.stringify(req.body)
|
||||
}
|
||||
});
|
||||
next();
|
||||
checks.push({
|
||||
id: 'outgoing-response',
|
||||
name: 'OutgoingResponse',
|
||||
// TODO: include MCP method?
|
||||
description: `Sent ${res.statusCode} response`,
|
||||
status: 'INFO',
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
// TODO: Response body not available in express middleware
|
||||
statusCode: res.statusCode
|
||||
}
|
||||
});
|
||||
next();
|
||||
checks.push({
|
||||
id: 'outgoing-response',
|
||||
name: 'OutgoingResponse',
|
||||
// TODO: include MCP method?
|
||||
description: `Sent ${res.statusCode} response`,
|
||||
status: 'INFO',
|
||||
timestamp: new Date().toISOString(),
|
||||
details: {
|
||||
// TODO: Response body not available in express middleware
|
||||
statusCode: res.statusCode
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/mcp', async (req: Request, res: Response) => {
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined
|
||||
});
|
||||
await server.connect(transport);
|
||||
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
app.post('/mcp', async (req: Request, res: Response) => {
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined
|
||||
});
|
||||
await server.connect(transport);
|
||||
|
||||
return app;
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
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[] = [];
|
||||
name = 'tools_call';
|
||||
description = 'Tests calling tools with various parameter types';
|
||||
private app: express.Application | null = null;
|
||||
private httpServer: any = null;
|
||||
private checks: ConformanceCheck[] = [];
|
||||
|
||||
async start(): Promise<ScenarioUrls> {
|
||||
this.checks = [];
|
||||
this.app = createServer(this.checks);
|
||||
this.httpServer = this.app.listen(0);
|
||||
const port = this.httpServer.address().port;
|
||||
return { serverUrl: `http://localhost:${port}/mcp` };
|
||||
async start(): Promise<ScenarioUrls> {
|
||||
this.checks = [];
|
||||
this.app = createServer(this.checks);
|
||||
this.httpServer = this.app.listen(0);
|
||||
const port = this.httpServer.address().port;
|
||||
return { serverUrl: `http://localhost:${port}/mcp` };
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.httpServer) {
|
||||
await new Promise((resolve) => this.httpServer.close(resolve));
|
||||
this.httpServer = null;
|
||||
}
|
||||
this.app = null;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.httpServer) {
|
||||
await new Promise(resolve => this.httpServer.close(resolve));
|
||||
this.httpServer = null;
|
||||
}
|
||||
this.app = null;
|
||||
}
|
||||
|
||||
getChecks(): ConformanceCheck[] {
|
||||
const expectedSlugs = ['tool-add-numbers'];
|
||||
// add a failure if not in there already
|
||||
for (const slug of expectedSlugs) {
|
||||
if (!this.checks.find(c => c.id === slug)) {
|
||||
// TODO: this is duplicated from above, refactor
|
||||
this.checks.push({
|
||||
id: slug,
|
||||
name: `ToolAddNumbers`,
|
||||
description: `Validates that the add_numbers tool works correctly`,
|
||||
status: 'FAILURE',
|
||||
timestamp: new Date().toISOString(),
|
||||
details: { message: 'Tool was not called by client' },
|
||||
specReferences: [
|
||||
{
|
||||
id: 'MCP-Tools',
|
||||
url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools'
|
||||
}
|
||||
]
|
||||
});
|
||||
getChecks(): ConformanceCheck[] {
|
||||
const expectedSlugs = ['tool-add-numbers'];
|
||||
// add a failure if not in there already
|
||||
for (const slug of expectedSlugs) {
|
||||
if (!this.checks.find((c) => c.id === slug)) {
|
||||
// TODO: this is duplicated from above, refactor
|
||||
this.checks.push({
|
||||
id: slug,
|
||||
name: `ToolAddNumbers`,
|
||||
description: `Validates that the add_numbers tool works correctly`,
|
||||
status: 'FAILURE',
|
||||
timestamp: new Date().toISOString(),
|
||||
details: { message: 'Tool was not called by client' },
|
||||
specReferences: [
|
||||
{
|
||||
id: 'MCP-Tools',
|
||||
url: 'https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools'
|
||||
}
|
||||
}
|
||||
return this.checks;
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.checks;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,97 +5,100 @@ import { ToolsCallScenario } from './client/tools_call.js';
|
|||
// Import all new server test scenarios
|
||||
import { ServerInitializeScenario } from './server/lifecycle.js';
|
||||
|
||||
import { LoggingSetLevelScenario, CompletionCompleteScenario } from './server/utils.js';
|
||||
import {
|
||||
LoggingSetLevelScenario,
|
||||
CompletionCompleteScenario
|
||||
} from './server/utils.js';
|
||||
|
||||
import {
|
||||
ToolsListScenario,
|
||||
ToolsCallSimpleTextScenario,
|
||||
ToolsCallImageScenario,
|
||||
ToolsCallMultipleContentTypesScenario,
|
||||
ToolsCallWithLoggingScenario,
|
||||
ToolsCallErrorScenario,
|
||||
ToolsCallWithProgressScenario,
|
||||
ToolsCallSamplingScenario,
|
||||
ToolsCallElicitationScenario,
|
||||
ToolsCallAudioScenario,
|
||||
ToolsCallEmbeddedResourceScenario
|
||||
ToolsListScenario,
|
||||
ToolsCallSimpleTextScenario,
|
||||
ToolsCallImageScenario,
|
||||
ToolsCallMultipleContentTypesScenario,
|
||||
ToolsCallWithLoggingScenario,
|
||||
ToolsCallErrorScenario,
|
||||
ToolsCallWithProgressScenario,
|
||||
ToolsCallSamplingScenario,
|
||||
ToolsCallElicitationScenario,
|
||||
ToolsCallAudioScenario,
|
||||
ToolsCallEmbeddedResourceScenario
|
||||
} from './server/tools.js';
|
||||
|
||||
import {
|
||||
ResourcesListScenario,
|
||||
ResourcesReadTextScenario,
|
||||
ResourcesReadBinaryScenario,
|
||||
ResourcesTemplateReadScenario,
|
||||
ResourcesSubscribeScenario,
|
||||
ResourcesUnsubscribeScenario
|
||||
ResourcesListScenario,
|
||||
ResourcesReadTextScenario,
|
||||
ResourcesReadBinaryScenario,
|
||||
ResourcesTemplateReadScenario,
|
||||
ResourcesSubscribeScenario,
|
||||
ResourcesUnsubscribeScenario
|
||||
} from './server/resources.js';
|
||||
|
||||
import {
|
||||
PromptsListScenario,
|
||||
PromptsGetSimpleScenario,
|
||||
PromptsGetWithArgsScenario,
|
||||
PromptsGetEmbeddedResourceScenario,
|
||||
PromptsGetWithImageScenario
|
||||
PromptsListScenario,
|
||||
PromptsGetSimpleScenario,
|
||||
PromptsGetWithArgsScenario,
|
||||
PromptsGetEmbeddedResourceScenario,
|
||||
PromptsGetWithImageScenario
|
||||
} from './server/prompts.js';
|
||||
|
||||
export const scenarios = new Map<string, Scenario>([
|
||||
['initialize', new InitializeScenario()],
|
||||
['tools-call', new ToolsCallScenario()]
|
||||
['initialize', new InitializeScenario()],
|
||||
['tools-call', new ToolsCallScenario()]
|
||||
]);
|
||||
|
||||
export const clientScenarios = new Map<string, ClientScenario>([
|
||||
// Lifecycle scenarios
|
||||
['server-initialize', new ServerInitializeScenario()],
|
||||
// Lifecycle scenarios
|
||||
['server-initialize', new ServerInitializeScenario()],
|
||||
|
||||
// Utilities scenarios
|
||||
['logging-set-level', new LoggingSetLevelScenario()],
|
||||
['completion-complete', new CompletionCompleteScenario()],
|
||||
// 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()],
|
||||
// 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()],
|
||||
// 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()]
|
||||
// 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);
|
||||
scenarios.set(name, scenario);
|
||||
}
|
||||
|
||||
export function getScenario(name: string): Scenario | undefined {
|
||||
return scenarios.get(name);
|
||||
return scenarios.get(name);
|
||||
}
|
||||
|
||||
export function getClientScenario(name: string): ClientScenario | undefined {
|
||||
return clientScenarios.get(name);
|
||||
return clientScenarios.get(name);
|
||||
}
|
||||
|
||||
export function listScenarios(): string[] {
|
||||
return Array.from(scenarios.keys());
|
||||
return Array.from(scenarios.keys());
|
||||
}
|
||||
|
||||
export function listClientScenarios(): string[] {
|
||||
return Array.from(clientScenarios.keys());
|
||||
return Array.from(clientScenarios.keys());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,80 +4,91 @@
|
|||
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import { LoggingMessageNotificationSchema, ProgressNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
import {
|
||||
LoggingMessageNotificationSchema,
|
||||
ProgressNotificationSchema
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
export interface MCPClientConnection {
|
||||
client: Client;
|
||||
close: () => Promise<void>;
|
||||
client: Client;
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and connect an MCP client to a server
|
||||
*/
|
||||
export async function connectToServer(serverUrl: string): Promise<MCPClientConnection> {
|
||||
const client = new Client(
|
||||
{
|
||||
name: 'conformance-test-client',
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
// Client capabilities
|
||||
sampling: {},
|
||||
elicitation: {}
|
||||
}
|
||||
}
|
||||
);
|
||||
export async function connectToServer(
|
||||
serverUrl: string
|
||||
): Promise<MCPClientConnection> {
|
||||
const client = new Client(
|
||||
{
|
||||
name: 'conformance-test-client',
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
// Client capabilities
|
||||
sampling: {},
|
||||
elicitation: {}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(new URL(serverUrl));
|
||||
const transport = new StreamableHTTPClientTransport(new URL(serverUrl));
|
||||
|
||||
await client.connect(transport);
|
||||
await client.connect(transport);
|
||||
|
||||
return {
|
||||
client,
|
||||
close: async () => {
|
||||
await client.close();
|
||||
}
|
||||
};
|
||||
return {
|
||||
client,
|
||||
close: async () => {
|
||||
await client.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to collect notifications (logging and progress)
|
||||
*/
|
||||
export class NotificationCollector {
|
||||
private loggingNotifications: any[] = [];
|
||||
private progressNotifications: any[] = [];
|
||||
private loggingNotifications: any[] = [];
|
||||
private progressNotifications: any[] = [];
|
||||
|
||||
constructor(client: Client) {
|
||||
// Set up notification handler for logging messages
|
||||
client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
|
||||
this.loggingNotifications.push(notification);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
// 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 logging notifications
|
||||
*/
|
||||
getLoggingNotifications(): any[] {
|
||||
return this.loggingNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all collected progress notifications
|
||||
*/
|
||||
getProgressNotifications(): any[] {
|
||||
return this.progressNotifications;
|
||||
}
|
||||
/**
|
||||
* Get all collected progress notifications
|
||||
*/
|
||||
getProgressNotifications(): any[] {
|
||||
return this.progressNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all notifications (for backward compatibility)
|
||||
*/
|
||||
getNotifications(): any[] {
|
||||
return this.loggingNotifications;
|
||||
}
|
||||
/**
|
||||
* Get all notifications (for backward compatibility)
|
||||
*/
|
||||
getNotifications(): any[] {
|
||||
return this.loggingNotifications;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,53 +6,55 @@ 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';
|
||||
name = 'server-initialize';
|
||||
description = 'Test basic server initialization handshake';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
// 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
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,345 +6,352 @@ 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';
|
||||
name = 'prompts-list';
|
||||
description = 'Test listing available prompts';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
const result = await connection.client.listPrompts();
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
// 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');
|
||||
}
|
||||
|
||||
return checks;
|
||||
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';
|
||||
name = 'prompts-get-simple';
|
||||
description = 'Test getting a simple prompt without arguments';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
const result = await connection.client.getPrompt({
|
||||
name: 'test_simple_prompt'
|
||||
});
|
||||
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');
|
||||
// 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`);
|
||||
});
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
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';
|
||||
name = 'prompts-get-with-args';
|
||||
description = 'Test parameterized prompt';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
const result = await connection.client.getPrompt({
|
||||
name: 'test_prompt_with_arguments',
|
||||
arguments: {
|
||||
arg1: 'testValue1',
|
||||
arg2: 'testValue2'
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
// 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';
|
||||
name = 'prompts-get-embedded-resource';
|
||||
description = 'Test prompt with embedded resource content';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
const result = await connection.client.getPrompt({
|
||||
name: 'test_prompt_with_embedded_resource',
|
||||
arguments: {
|
||||
resourceUri: 'test://example-resource'
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
// 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';
|
||||
name = 'prompts-get-with-image';
|
||||
description = 'Test prompt with image content';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
const result = await connection.client.getPrompt({
|
||||
name: 'test_prompt_with_image'
|
||||
});
|
||||
const result = await connection.client.getPrompt({
|
||||
name: 'test_prompt_with_image'
|
||||
});
|
||||
|
||||
// Validate response
|
||||
const errors: string[] = [];
|
||||
if (!result.messages) errors.push('Missing messages array');
|
||||
// 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
|
||||
);
|
||||
// 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');
|
||||
}
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,384 +6,386 @@ 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';
|
||||
name = 'resources-list';
|
||||
description = 'Test listing available resources';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
const result = await connection.client.listResources();
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
// 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');
|
||||
}
|
||||
|
||||
return checks;
|
||||
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';
|
||||
name = 'resources-read-text';
|
||||
description = 'Test reading text resource';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
const result = await connection.client.readResource({
|
||||
uri: 'test://static-text'
|
||||
});
|
||||
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');
|
||||
// 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');
|
||||
}
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
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';
|
||||
name = 'resources-read-binary';
|
||||
description = 'Test reading binary resource';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
const result = await connection.client.readResource({
|
||||
uri: 'test://static-binary'
|
||||
});
|
||||
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');
|
||||
// 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');
|
||||
}
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
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';
|
||||
name = 'resources-templates-read';
|
||||
description = 'Test reading resource from template';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
const result = await connection.client.readResource({
|
||||
uri: 'test://template/123/data'
|
||||
});
|
||||
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');
|
||||
// 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');
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
return checks;
|
||||
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';
|
||||
name = 'resources-subscribe';
|
||||
description = 'Test subscribing to resource updates';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
await connection.client.subscribeResource({
|
||||
uri: 'test://watched-resource'
|
||||
});
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
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;
|
||||
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';
|
||||
name = 'resources-unsubscribe';
|
||||
description = 'Test unsubscribing from resource';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
// First subscribe
|
||||
await connection.client.subscribeResource({
|
||||
uri: 'test://watched-resource'
|
||||
});
|
||||
// First subscribe
|
||||
await connection.client.subscribeResource({
|
||||
uri: 'test://watched-resource'
|
||||
});
|
||||
|
||||
// Then unsubscribe
|
||||
await connection.client.unsubscribeResource({
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,81 +2,86 @@ import { ClientScenario, ConformanceCheck } from '../../types.js';
|
|||
import { serverChecks } from '../../checks/index.js';
|
||||
|
||||
export class ServerInitializeClientScenario implements ClientScenario {
|
||||
name = 'server-initialize';
|
||||
description = 'Acts as MCP client to test external server initialization';
|
||||
name = 'server-initialize';
|
||||
description = 'Acts as MCP client to test external server initialization';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const response = await fetch(serverUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: 'initialize',
|
||||
params: {
|
||||
protocolVersion: '2025-06-18',
|
||||
capabilities: {},
|
||||
clientInfo: {
|
||||
name: 'conformance-test-client',
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const responseBody = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}. Response body: ${responseBody}`);
|
||||
try {
|
||||
const response = await fetch(serverUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: 'initialize',
|
||||
params: {
|
||||
protocolVersion: '2025-06-18',
|
||||
capabilities: {},
|
||||
clientInfo: {
|
||||
name: 'conformance-test-client',
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
if (!response.ok) {
|
||||
const responseBody = await response.text();
|
||||
throw new Error(
|
||||
`HTTP ${response.status}: ${response.statusText}. Response body: ${responseBody}`
|
||||
);
|
||||
}
|
||||
|
||||
// Handle SSE format
|
||||
let result;
|
||||
if (responseText.startsWith('event:') || responseText.includes('\ndata:')) {
|
||||
// Parse SSE format - extract JSON from data: lines
|
||||
const lines = responseText.split('\n');
|
||||
const dataLines = lines.filter(line => line.startsWith('data: '));
|
||||
if (dataLines.length > 0) {
|
||||
const jsonData = dataLines[0].substring(6); // Remove 'data: ' prefix
|
||||
result = JSON.parse(jsonData);
|
||||
} else {
|
||||
throw new Error(`SSE response without data line: ${responseText}`);
|
||||
}
|
||||
} else {
|
||||
// Regular JSON response
|
||||
result = JSON.parse(responseText);
|
||||
}
|
||||
const responseText = await response.text();
|
||||
|
||||
const check = serverChecks.createServerInitializationCheck(result);
|
||||
checks.push(check);
|
||||
} catch (error) {
|
||||
checks.push({
|
||||
id: 'server-initialize-request',
|
||||
name: 'ServerInitializeRequest',
|
||||
description: 'Tests server response to initialize request',
|
||||
status: 'FAILURE',
|
||||
timestamp: new Date().toISOString(),
|
||||
errorMessage: `Failed to send initialize request: ${error instanceof Error ? error.message : String(error)}`,
|
||||
details: {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
serverUrl
|
||||
},
|
||||
specReferences: [
|
||||
{
|
||||
id: 'MCP-Initialize',
|
||||
url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization'
|
||||
}
|
||||
]
|
||||
});
|
||||
// Handle SSE format
|
||||
let result;
|
||||
if (
|
||||
responseText.startsWith('event:') ||
|
||||
responseText.includes('\ndata:')
|
||||
) {
|
||||
// Parse SSE format - extract JSON from data: lines
|
||||
const lines = responseText.split('\n');
|
||||
const dataLines = lines.filter((line) => line.startsWith('data: '));
|
||||
if (dataLines.length > 0) {
|
||||
const jsonData = dataLines[0].substring(6); // Remove 'data: ' prefix
|
||||
result = JSON.parse(jsonData);
|
||||
} else {
|
||||
throw new Error(`SSE response without data line: ${responseText}`);
|
||||
}
|
||||
} else {
|
||||
// Regular JSON response
|
||||
result = JSON.parse(responseText);
|
||||
}
|
||||
|
||||
return checks;
|
||||
const check = serverChecks.createServerInitializationCheck(result);
|
||||
checks.push(check);
|
||||
} catch (error) {
|
||||
checks.push({
|
||||
id: 'server-initialize-request',
|
||||
name: 'ServerInitializeRequest',
|
||||
description: 'Tests server response to initialize request',
|
||||
status: 'FAILURE',
|
||||
timestamp: new Date().toISOString(),
|
||||
errorMessage: `Failed to send initialize request: ${error instanceof Error ? error.message : String(error)}`,
|
||||
details: {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
serverUrl
|
||||
},
|
||||
specReferences: [
|
||||
{
|
||||
id: 'MCP-Initialize',
|
||||
url: 'https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
return checks;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,131 +6,133 @@ 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';
|
||||
name = 'logging-set-level';
|
||||
description = 'Test setting logging level';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
|
||||
// Send logging/setLevel request
|
||||
const result = await connection.client.setLoggingLevel('info');
|
||||
// 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');
|
||||
}
|
||||
// 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'
|
||||
}
|
||||
]
|
||||
});
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
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';
|
||||
name = 'completion-complete';
|
||||
description = 'Test completion endpoint';
|
||||
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
async run(serverUrl: string): Promise<ConformanceCheck[]> {
|
||||
const checks: ConformanceCheck[] = [];
|
||||
|
||||
try {
|
||||
const connection = await connectToServer(serverUrl);
|
||||
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'
|
||||
}
|
||||
]
|
||||
});
|
||||
// Send completion/complete request
|
||||
const result = await connection.client.complete({
|
||||
ref: {
|
||||
type: 'ref/prompt',
|
||||
name: 'test_prompt_with_arguments'
|
||||
},
|
||||
argument: {
|
||||
name: 'arg1',
|
||||
value: 'test'
|
||||
}
|
||||
});
|
||||
|
||||
return checks;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue