Skip to main content
Glama
tool-result-matcher.ts8.05 kB
// Using _CallToolResult to satisfy linter requirement for unused vars to have _ prefix import type { ExpectationResult } from '@vitest/expect'; import { expect } from 'vitest'; interface ContentText { type: 'text'; text?: string | RegExp; } interface ContentResource { type: 'resource'; resource?: | { text: string | RegExp; json?: never; } | { json?: Record<string, unknown>; text?: never; }; } interface ResourceContentInfo { mimeType?: string; text?: string | RegExp; blob?: string | RegExp; uri?: string | RegExp; } export interface ExpectedToolResult { isError?: boolean; tool?: (ContentText | ContentResource)[]; resource?: ResourceContentInfo[]; } interface ActualContentItem { type: string; text?: string; resource?: { text?: string; }; } function matchContentItem( actual: ActualContentItem | undefined, expected: ContentText | ContentResource, ): boolean { if (!actual) { return false; } if (actual.type !== expected.type) { return false; } if (actual.type === 'text' && expected.type === 'text') { if (expected.text !== undefined) { // Check if it's a RegExp or a string if (expected.text instanceof RegExp) { return !!actual.text && expected.text.test(actual.text); } else { // Plain string comparison return actual.text === expected.text; } } return true; } if (actual.type === 'resource' && expected.type === 'resource') { const actualResource = actual.resource; const expectedResource = expected.resource; if (!expectedResource || !actualResource) { return !expectedResource && !actualResource; } if (expectedResource.text !== undefined && actualResource.text !== undefined) { // Check if it's a RegExp or a string if (expectedResource.text instanceof RegExp) { return expectedResource.text.test(actualResource.text); } else { // Plain string comparison if (actualResource.text !== expectedResource.text) { return false; } } } else if (expectedResource.text !== undefined) { return false; } if (expectedResource.json !== undefined && actualResource.text) { try { const actualJson = JSON.parse(actualResource.text); return JSON.stringify(actualJson) === JSON.stringify(expectedResource.json); } catch { return false; } } return true; } return false; } /* --- Register the matcher with Vitest --------------------------- */ expect.extend({ toMatchMcpResult(received: unknown, expected: ExpectedToolResult): ExpectationResult { if (!received || typeof received !== 'object') { return { pass: false, actual: received, expected, message: () => `Expected a CallToolResult object but got ${typeof received}`, }; } const result = received as Record<string, unknown>; // Verify isError property if specified if (expected.isError !== undefined && result['isError'] !== expected.isError) { return { pass: false, actual: result['isError'], expected: expected.isError, message: () => `Expected isError to be ${expected.isError} but got ${result['isError']}`, }; } // Tool result content if (expected.tool !== undefined) { const actualContent = result['content'] as ActualContentItem[] | undefined; const expectedContent = expected.tool; if (!actualContent) { return { pass: false, actual: actualContent, expected: expected.tool, message: () => `Expected content array to be present but got ${actualContent}`, }; } if (actualContent.length !== expectedContent.length) { return { pass: false, actual: actualContent.length, expected: expectedContent.length, message: () => `Expected content array to have length ${expectedContent.length} but got ${actualContent.length}`, }; } // Check each content item for (let i = 0; i < expectedContent.length; i++) { const expectedItem = expectedContent[i]; const actualItem = actualContent[i]; if (expectedItem && !matchContentItem(actualItem, expectedItem)) { return { pass: false, actual: actualItem, expected: expectedItem, message: () => `Content item at index ${i} doesn't match expected value`, }; } } } // Resource response contents if (expected.resource !== undefined) { const actualContents = result['contents'] as Record<string, unknown>[] | undefined; const expectedContents = expected.resource; if (!actualContents) { return { pass: false, actual: actualContents, expected: expectedContents, message: () => `Expected contents array to be present but got ${actualContents}`, }; } if (actualContents.length !== expectedContents.length) { return { pass: false, actual: actualContents.length, expected: expectedContents.length, message: () => `Expected contents array to have length ${expectedContents.length} but got ${actualContents.length}`, }; } // Check each content item for (let i = 0; i < expectedContents.length; i++) { const expectedItem = expectedContents[i]; const actualItem = actualContents[i]; if (!expectedItem || !actualItem) { continue; } if ( expectedItem.mimeType !== undefined && actualItem['mimeType'] !== expectedItem.mimeType ) { return { pass: false, actual: actualItem['mimeType'], expected: expectedItem.mimeType, message: () => `Contents item at index ${i} has wrong mimeType`, }; } if (expectedItem.text !== undefined && !matches(actualItem['text'], expectedItem.text)) { return { pass: false, actual: actualItem['text'], expected: expectedItem.text, message: () => `Contents item at index ${i} has wrong text content`, }; } if (expectedItem.uri !== undefined && !matches(actualItem['uri'], expectedItem.uri)) { return { pass: false, actual: actualItem['uri'], expected: expectedItem.uri, message: () => `Contents item at index ${i} has wrong uri`, }; } if (expectedItem.blob !== undefined) { if (actualItem['blob'] === undefined) { return { pass: false, actual: actualItem['blob'], expected: 'defined blob', message: () => `Contents item at index ${i} is missing blob property`, }; } if (typeof actualItem['blob'] !== 'string') { return { pass: false, actual: typeof actualItem['blob'], expected: 'string', message: () => `Contents item at index ${i} blob property should be a string`, }; } } } } // Everything passed return { pass: true, message: () => 'CallToolResult matched expected shape', }; }, }); function matches(actual: unknown, expected: string | RegExp): boolean { if (expected instanceof RegExp) { if (typeof actual !== 'string') { return false; } return expected.test(actual); } else { return actual === expected; } } /* --- Type declarations so TS recognises the matcher ------------- */ declare module 'vitest' { interface Assertion { toMatchMcpResult(expected: ExpectedToolResult): void; } interface AsymmetricMatchersContaining { toMatchMcpResult(expected: ExpectedToolResult): void; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wycats/mcpify'

If you have feedback or need assistance with the MCP directory API, please join our Discord server