* add cimd test and negative test * Improve auth-test-no-cimd.ts documentation for clarity Address review feedback: expanded comments to better explain what CIMD is, why this client is non-compliant, and what behavior it's testing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove .js extensions from relative imports tsx doesn't require .js extensions for TypeScript imports. Keeps .js for @modelcontextprotocol/sdk imports as those are external packages. Also improves auth-test-no-cimd.ts documentation for clarity. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Skip auth/basic-cimd test until SDK support lands Waiting on typescript-sdk PR #1127 for CIMD support. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix trailing whitespace in auth-test-no-cimd.ts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
import { Request, Response, NextFunction } from 'express';
|
|
import { ConformanceCheck } from '../types';
|
|
|
|
export interface LoggerOptions {
|
|
incomingId: string;
|
|
outgoingId: string;
|
|
mcpRoute?: string;
|
|
}
|
|
|
|
export function createRequestLogger(
|
|
checks: ConformanceCheck[],
|
|
options: LoggerOptions
|
|
) {
|
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
// Log incoming request
|
|
let requestDescription = `Received ${req.method} request for ${req.path}`;
|
|
const requestDetails: any = {
|
|
method: req.method,
|
|
path: req.path,
|
|
body: req.body
|
|
};
|
|
|
|
// Add query parameters to details if they exist
|
|
if (Object.keys(req.query).length > 0) {
|
|
requestDetails.query = req.query;
|
|
}
|
|
|
|
// Extract MCP method if this is the MCP route
|
|
if (
|
|
options.mcpRoute &&
|
|
req.path === options.mcpRoute &&
|
|
req.get('content-type')?.includes('application/json') &&
|
|
req.body &&
|
|
typeof req.body === 'object' &&
|
|
req.body.method
|
|
) {
|
|
const mcpMethod = req.body.method;
|
|
requestDescription += ` (method: ${mcpMethod})`;
|
|
requestDetails.mcpMethod = mcpMethod;
|
|
}
|
|
|
|
checks.push({
|
|
id: options.incomingId,
|
|
name:
|
|
options.incomingId.charAt(0).toUpperCase() +
|
|
options.incomingId.slice(1),
|
|
description: requestDescription,
|
|
status: 'INFO',
|
|
timestamp: new Date().toISOString(),
|
|
details: requestDetails
|
|
});
|
|
|
|
// Capture response body
|
|
const oldWrite = res.write.bind(res);
|
|
const oldEnd = res.end.bind(res);
|
|
const chunks: (Buffer | string)[] = [];
|
|
|
|
(res.write as any) = function (chunk: any, ...args: any[]) {
|
|
chunks.push(chunk);
|
|
return oldWrite(chunk, ...args);
|
|
};
|
|
|
|
(res.end as any) = function (chunk?: any, ...args: any[]) {
|
|
if (chunk) {
|
|
chunks.push(chunk);
|
|
}
|
|
|
|
const buffers = chunks.map((c) =>
|
|
typeof c === 'string' ? Buffer.from(c) : c
|
|
);
|
|
const body = Buffer.concat(buffers).toString('utf8');
|
|
let responseDescription = `Sent ${res.statusCode} response for ${req.method} ${req.path}`;
|
|
const responseDetails: any = {
|
|
method: req.method,
|
|
path: req.path,
|
|
statusCode: res.statusCode
|
|
};
|
|
|
|
// Include MCP method in response log if present
|
|
if (requestDetails.mcpMethod) {
|
|
responseDescription += ` (method: ${requestDetails.mcpMethod})`;
|
|
responseDetails.mcpMethod = requestDetails.mcpMethod;
|
|
}
|
|
|
|
// Add response headers
|
|
const headers = res.getHeaders();
|
|
if (Object.keys(headers).length > 0) {
|
|
responseDetails.headers = headers;
|
|
}
|
|
|
|
// Add response body if available
|
|
if (body) {
|
|
try {
|
|
responseDetails.body = JSON.parse(body);
|
|
} catch {
|
|
responseDetails.body = body;
|
|
}
|
|
}
|
|
|
|
checks.push({
|
|
id: options.outgoingId,
|
|
name:
|
|
options.outgoingId.charAt(0).toUpperCase() +
|
|
options.outgoingId.slice(1),
|
|
description: responseDescription,
|
|
status: 'INFO',
|
|
timestamp: new Date().toISOString(),
|
|
details: responseDetails
|
|
});
|
|
|
|
return oldEnd(chunk, ...args);
|
|
};
|
|
|
|
next();
|
|
};
|
|
}
|