first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import type { McpToolResult } from './types';
|
||||
|
||||
export class MessageFormatter {
|
||||
static formatToolResult(result: unknown): McpToolResult {
|
||||
if (typeof result === 'object' && result !== null) {
|
||||
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
||||
}
|
||||
if (typeof result === 'string') {
|
||||
return { content: [{ type: 'text', text: result }] };
|
||||
}
|
||||
if (result === null || result === undefined) {
|
||||
return { content: [{ type: 'text', text: String(result) }] };
|
||||
}
|
||||
if (typeof result === 'number' || typeof result === 'boolean' || typeof result === 'bigint') {
|
||||
return { content: [{ type: 'text', text: result.toString() }] };
|
||||
}
|
||||
// Remaining types: symbol, function - convert to string representation
|
||||
return {
|
||||
content: [
|
||||
{ type: 'text', text: String(result as symbol | ((...args: unknown[]) => unknown)) },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static formatError(error: Error): McpToolResult {
|
||||
const errorDetails = [`${error.name}: ${error.message}`];
|
||||
if (error.stack) {
|
||||
errorDetails.push(error.stack);
|
||||
}
|
||||
return {
|
||||
isError: true,
|
||||
content: [{ type: 'text', text: errorDetails.join('\n') }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
JSONRPCMessageSchema,
|
||||
ListToolsRequestSchema,
|
||||
CallToolRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
import type { McpToolCallInfo } from './types';
|
||||
|
||||
export class MessageParser {
|
||||
static parse(body: string): JSONRPCMessage | undefined {
|
||||
try {
|
||||
const message: unknown = JSON.parse(body);
|
||||
return JSONRPCMessageSchema.parse(message);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static isToolCall(body: string): boolean {
|
||||
const message = this.parse(body);
|
||||
if (!message) return false;
|
||||
return (
|
||||
'method' in message &&
|
||||
'id' in message &&
|
||||
message.method === CallToolRequestSchema.shape.method.value
|
||||
);
|
||||
}
|
||||
|
||||
static isListToolsRequest(body: string): boolean {
|
||||
const message = this.parse(body);
|
||||
if (!message) return false;
|
||||
return (
|
||||
'method' in message &&
|
||||
'id' in message &&
|
||||
message.method === ListToolsRequestSchema.shape.method.value
|
||||
);
|
||||
}
|
||||
|
||||
static getRequestId(message: unknown): string | undefined {
|
||||
try {
|
||||
const parsed = JSONRPCMessageSchema.parse(message);
|
||||
return 'id' in parsed ? String(parsed.id) : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static extractToolCallInfo(body: string): McpToolCallInfo | undefined {
|
||||
const message = this.parse(body);
|
||||
if (!message) return undefined;
|
||||
|
||||
if (
|
||||
'method' in message &&
|
||||
'params' in message &&
|
||||
message.method === CallToolRequestSchema.shape.method.value
|
||||
) {
|
||||
const params = message.params;
|
||||
if (
|
||||
typeof params === 'object' &&
|
||||
params !== null &&
|
||||
'name' in params &&
|
||||
typeof params.name === 'string' &&
|
||||
'arguments' in params &&
|
||||
typeof params.arguments === 'object' &&
|
||||
params.arguments !== null
|
||||
) {
|
||||
return {
|
||||
toolName: params.name,
|
||||
arguments: params.arguments as Record<string, unknown>,
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
import { MessageFormatter } from '../MessageFormatter';
|
||||
|
||||
describe('MessageFormatter', () => {
|
||||
describe('formatToolResult', () => {
|
||||
it('should format object result as JSON string in content array', () => {
|
||||
const result = { data: 'value', count: 42 };
|
||||
expect(MessageFormatter.formatToolResult(result)).toEqual({
|
||||
content: [{ type: 'text', text: '{"data":"value","count":42}' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format string result directly without double-quoting', () => {
|
||||
expect(MessageFormatter.formatToolResult('hello world')).toEqual({
|
||||
content: [{ type: 'text', text: 'hello world' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format number as string', () => {
|
||||
expect(MessageFormatter.formatToolResult(42)).toEqual({
|
||||
content: [{ type: 'text', text: '42' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format zero as string', () => {
|
||||
expect(MessageFormatter.formatToolResult(0)).toEqual({
|
||||
content: [{ type: 'text', text: '0' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format negative number as string', () => {
|
||||
expect(MessageFormatter.formatToolResult(-123)).toEqual({
|
||||
content: [{ type: 'text', text: '-123' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format float as string', () => {
|
||||
expect(MessageFormatter.formatToolResult(3.14159)).toEqual({
|
||||
content: [{ type: 'text', text: '3.14159' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format boolean true as string', () => {
|
||||
expect(MessageFormatter.formatToolResult(true)).toEqual({
|
||||
content: [{ type: 'text', text: 'true' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format boolean false as string', () => {
|
||||
expect(MessageFormatter.formatToolResult(false)).toEqual({
|
||||
content: [{ type: 'text', text: 'false' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format null as JSON string "null"', () => {
|
||||
expect(MessageFormatter.formatToolResult(null)).toEqual({
|
||||
content: [{ type: 'text', text: 'null' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format undefined as string "undefined"', () => {
|
||||
expect(MessageFormatter.formatToolResult(undefined)).toEqual({
|
||||
content: [{ type: 'text', text: 'undefined' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle nested objects correctly', () => {
|
||||
const result = { outer: { inner: { deep: 'value' } } };
|
||||
const formatted = MessageFormatter.formatToolResult(result);
|
||||
expect(formatted.content[0].text).toBe(JSON.stringify(result));
|
||||
});
|
||||
|
||||
it('should handle arrays', () => {
|
||||
const result = [1, 2, 3];
|
||||
expect(MessageFormatter.formatToolResult(result)).toEqual({
|
||||
content: [{ type: 'text', text: '[1,2,3]' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
expect(MessageFormatter.formatToolResult([])).toEqual({
|
||||
content: [{ type: 'text', text: '[]' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty object', () => {
|
||||
expect(MessageFormatter.formatToolResult({})).toEqual({
|
||||
content: [{ type: 'text', text: '{}' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle array of objects', () => {
|
||||
const result = [{ id: 1 }, { id: 2 }];
|
||||
const formatted = MessageFormatter.formatToolResult(result);
|
||||
expect(formatted.content[0].text).toBe(JSON.stringify(result));
|
||||
});
|
||||
|
||||
it('should handle object with special characters in values', () => {
|
||||
const result = { message: 'Hello "world" with\nnewline' };
|
||||
const formatted = MessageFormatter.formatToolResult(result);
|
||||
expect(formatted.content[0].text).toBe(JSON.stringify(result));
|
||||
});
|
||||
|
||||
it('should handle empty string result', () => {
|
||||
expect(MessageFormatter.formatToolResult('')).toEqual({
|
||||
content: [{ type: 'text', text: '' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle string with unicode characters', () => {
|
||||
expect(MessageFormatter.formatToolResult('Hello')).toEqual({
|
||||
content: [{ type: 'text', text: 'Hello' }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatError', () => {
|
||||
it('should format error with isError flag set to true', () => {
|
||||
const error = new Error('Something went wrong');
|
||||
const result = MessageFormatter.formatError(error);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].type).toBe('text');
|
||||
expect(result.content[0].text).toContain('Error: Something went wrong');
|
||||
});
|
||||
|
||||
it('should handle error with empty message', () => {
|
||||
const error = new Error('');
|
||||
const result = MessageFormatter.formatError(error);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('Error: ');
|
||||
});
|
||||
|
||||
it('should handle error with special characters in message', () => {
|
||||
const error = new Error('Failed: "invalid" <value>');
|
||||
const result = MessageFormatter.formatError(error);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('Error: Failed: "invalid" <value>');
|
||||
});
|
||||
|
||||
it('should handle error with newlines in message', () => {
|
||||
const error = new Error('Line 1\nLine 2');
|
||||
const result = MessageFormatter.formatError(error);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('Error: Line 1\nLine 2');
|
||||
});
|
||||
|
||||
it('should handle TypeError', () => {
|
||||
const error = new TypeError('Cannot read property of undefined');
|
||||
const result = MessageFormatter.formatError(error);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('TypeError: Cannot read property of undefined');
|
||||
});
|
||||
|
||||
it('should handle custom error subclass', () => {
|
||||
class CustomError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'CustomError';
|
||||
}
|
||||
}
|
||||
const error = new CustomError('Custom error message');
|
||||
const result = MessageFormatter.formatError(error);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('CustomError: Custom error message');
|
||||
});
|
||||
|
||||
it('should include stack trace when available', () => {
|
||||
const error = new Error('Test error');
|
||||
const result = MessageFormatter.formatError(error);
|
||||
|
||||
expect(result.content[0].text).toContain('Error: Test error');
|
||||
expect(result.content[0].text).toContain('at ');
|
||||
});
|
||||
});
|
||||
});
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
import { MessageParser } from '../MessageParser';
|
||||
|
||||
describe('MessageParser', () => {
|
||||
describe('parse', () => {
|
||||
it('should parse valid JSONRPC 2.0 message with all fields', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"test","params":{}}';
|
||||
const result = MessageParser.parse(body);
|
||||
expect(result).toEqual({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: 'test',
|
||||
params: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse JSONRPC response message', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"result":{"data":"test"}}';
|
||||
const result = MessageParser.parse(body);
|
||||
expect(result).toEqual({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
result: { data: 'test' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse JSONRPC notification (no id)', () => {
|
||||
const body = '{"jsonrpc":"2.0","method":"notifications/test"}';
|
||||
const result = MessageParser.parse(body);
|
||||
expect(result).toEqual({
|
||||
jsonrpc: '2.0',
|
||||
method: 'notifications/test',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined for empty string', () => {
|
||||
expect(MessageParser.parse('')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for malformed JSON (missing closing brace)', () => {
|
||||
expect(MessageParser.parse('{"jsonrpc":"2.0"')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for valid JSON but invalid JSONRPC (missing jsonrpc field)', () => {
|
||||
expect(MessageParser.parse('{"id":1,"method":"test"}')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for JSONRPC 1.0 messages', () => {
|
||||
expect(MessageParser.parse('{"jsonrpc":"1.0","id":1}')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for plain JSON that is not JSONRPC', () => {
|
||||
expect(MessageParser.parse('{"name":"test","value":123}')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for array input', () => {
|
||||
expect(MessageParser.parse('[1,2,3]')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for primitive JSON values', () => {
|
||||
expect(MessageParser.parse('null')).toBeUndefined();
|
||||
expect(MessageParser.parse('123')).toBeUndefined();
|
||||
expect(MessageParser.parse('"string"')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isToolCall', () => {
|
||||
it('should return true for valid tools/call request', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"test","arguments":{}}}';
|
||||
expect(MessageParser.isToolCall(body)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for tools/call with string id', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":"abc-123","method":"tools/call","params":{"name":"test","arguments":{}}}';
|
||||
expect(MessageParser.isToolCall(body)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for tools/list request (different method)', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/list"}';
|
||||
expect(MessageParser.isToolCall(body)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for notification (no id field)', () => {
|
||||
const body = '{"jsonrpc":"2.0","method":"tools/call","params":{}}';
|
||||
expect(MessageParser.isToolCall(body)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for response (no method field)', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"result":{}}';
|
||||
expect(MessageParser.isToolCall(body)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for empty body', () => {
|
||||
expect(MessageParser.isToolCall('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for malformed JSON', () => {
|
||||
expect(MessageParser.isToolCall('{"jsonrpc":"2.0"')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for other MCP methods', () => {
|
||||
expect(MessageParser.isToolCall('{"jsonrpc":"2.0","id":1,"method":"initialize"}')).toBe(
|
||||
false,
|
||||
);
|
||||
expect(MessageParser.isToolCall('{"jsonrpc":"2.0","id":1,"method":"resources/list"}')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isListToolsRequest', () => {
|
||||
it('should return true for valid tools/list request', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/list"}';
|
||||
expect(MessageParser.isListToolsRequest(body)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for tools/list with params', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}';
|
||||
expect(MessageParser.isListToolsRequest(body)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for tools/call request', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"test","arguments":{}}}';
|
||||
expect(MessageParser.isListToolsRequest(body)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for notification (no id)', () => {
|
||||
const body = '{"jsonrpc":"2.0","method":"tools/list"}';
|
||||
expect(MessageParser.isListToolsRequest(body)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for empty body', () => {
|
||||
expect(MessageParser.isListToolsRequest('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequestId', () => {
|
||||
it('should extract numeric id and return as string', () => {
|
||||
const message = { jsonrpc: '2.0', id: 42, method: 'test' };
|
||||
expect(MessageParser.getRequestId(message)).toBe('42');
|
||||
});
|
||||
|
||||
it('should extract string id as-is', () => {
|
||||
const message = { jsonrpc: '2.0', id: 'abc-123', method: 'test' };
|
||||
expect(MessageParser.getRequestId(message)).toBe('abc-123');
|
||||
});
|
||||
|
||||
it('should extract id from response message', () => {
|
||||
const message = { jsonrpc: '2.0', id: 99, result: {} };
|
||||
expect(MessageParser.getRequestId(message)).toBe('99');
|
||||
});
|
||||
|
||||
it('should return undefined for notification (no id)', () => {
|
||||
const message = { jsonrpc: '2.0', method: 'test' };
|
||||
expect(MessageParser.getRequestId(message)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for invalid message structure', () => {
|
||||
expect(MessageParser.getRequestId({ invalid: true })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for null input', () => {
|
||||
expect(MessageParser.getRequestId(null)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for string input', () => {
|
||||
expect(MessageParser.getRequestId('string')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for undefined input', () => {
|
||||
expect(MessageParser.getRequestId(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for array input', () => {
|
||||
expect(MessageParser.getRequestId([1, 2, 3])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractToolCallInfo', () => {
|
||||
it('should extract tool name and arguments from valid call', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"London"}}}';
|
||||
const result = MessageParser.extractToolCallInfo(body);
|
||||
expect(result).toEqual({
|
||||
toolName: 'get_weather',
|
||||
arguments: { city: 'London' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty arguments object', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"no_args_tool","arguments":{}}}';
|
||||
const result = MessageParser.extractToolCallInfo(body);
|
||||
expect(result).toEqual({
|
||||
toolName: 'no_args_tool',
|
||||
arguments: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle complex nested arguments', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"complex_tool","arguments":{"nested":{"deep":{"value":123}},"array":[1,2,3]}}}';
|
||||
const result = MessageParser.extractToolCallInfo(body);
|
||||
expect(result).toEqual({
|
||||
toolName: 'complex_tool',
|
||||
arguments: { nested: { deep: { value: 123 } }, array: [1, 2, 3] },
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined when params.name is missing', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"arguments":{}}}';
|
||||
expect(MessageParser.extractToolCallInfo(body)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when params.arguments is missing', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"test"}}';
|
||||
expect(MessageParser.extractToolCallInfo(body)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when params.arguments is null', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"test","arguments":null}}';
|
||||
expect(MessageParser.extractToolCallInfo(body)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when params.arguments is not an object', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"test","arguments":"string"}}';
|
||||
expect(MessageParser.extractToolCallInfo(body)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when params.name is not a string', () => {
|
||||
const body =
|
||||
'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":123,"arguments":{}}}';
|
||||
expect(MessageParser.extractToolCallInfo(body)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for non-tool-call messages', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/list"}';
|
||||
expect(MessageParser.extractToolCallInfo(body)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when params is missing', () => {
|
||||
const body = '{"jsonrpc":"2.0","id":1,"method":"tools/call"}';
|
||||
expect(MessageParser.extractToolCallInfo(body)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for empty body', () => {
|
||||
expect(MessageParser.extractToolCallInfo('')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for malformed JSON', () => {
|
||||
expect(MessageParser.extractToolCallInfo('{"invalid')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './types';
|
||||
export * from './MessageParser';
|
||||
export * from './MessageFormatter';
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
export interface McpToolCallInfo {
|
||||
toolName: string;
|
||||
arguments: Record<string, unknown>;
|
||||
sourceNodeName?: string;
|
||||
}
|
||||
|
||||
export interface McpToolResult {
|
||||
[key: string]: unknown;
|
||||
content: Array<{ type: string; text: string }>;
|
||||
isError?: boolean;
|
||||
}
|
||||
|
||||
export type { JSONRPCMessage };
|
||||
|
||||
export const MCP_LIST_TOOLS_REQUEST_MARKER = { _listToolsRequest: true } as const;
|
||||
Reference in New Issue
Block a user