Skip to main content
Glama

Git MCP Server

assertions.ts6.23 kB
/** * @fileoverview Custom test assertions for git tool testing. * @module tests/mcp-server/tools/definitions/helpers/assertions */ import { expect } from 'vitest'; import type { ContentBlock } from '@modelcontextprotocol/sdk/types.js'; import { McpError, JsonRpcErrorCode } from '@/types-global/errors.js'; /** * Assert that a value is a valid McpError with expected properties */ export function assertMcpError( error: unknown, expectedCode: JsonRpcErrorCode, messagePattern?: string | RegExp, ): asserts error is McpError { expect(error).toBeInstanceOf(McpError); const mcpError = error as McpError; expect(mcpError.code).toBe(expectedCode); if (messagePattern) { if (typeof messagePattern === 'string') { expect(mcpError.message).toContain(messagePattern); } else { expect(mcpError.message).toMatch(messagePattern); } } } /** * Assert that content blocks contain text content */ export function assertTextContent( content: ContentBlock[], expectedPattern: string | RegExp, ): void { expect(content).toHaveLength(1); expect(content[0]).toHaveProperty('type', 'text'); const textContent = (content[0] as { type: 'text'; text: string }).text; if (typeof expectedPattern === 'string') { expect(textContent).toContain(expectedPattern); } else { expect(textContent).toMatch(expectedPattern); } } /** * Assert that content blocks contain properly formatted markdown */ export function assertMarkdownContent( content: ContentBlock[], expectedSections: string[], ): void { expect(content).toHaveLength(1); expect(content[0]).toHaveProperty('type', 'text'); const textContent = (content[0] as { type: 'text'; text: string }).text; // Check for markdown sections for (const section of expectedSections) { expect(textContent).toContain(section); } } /** * Assert that a tool output has the expected structure */ export function assertToolOutput<T extends Record<string, unknown>>( output: unknown, expectedFields: (keyof T)[], ): asserts output is T { expect(output).toBeDefined(); expect(typeof output).toBe('object'); expect(output).not.toBeNull(); for (const field of expectedFields) { expect(output).toHaveProperty(field as string); } } /** * Assert that provider was called with expected context */ export function assertProviderCalledWithContext( providerCall: unknown[], expectedWorkingDir: string, expectedTenantId: string, ): void { expect(providerCall).toHaveLength(2); const [_options, context] = providerCall; expect(context).toMatchObject({ workingDirectory: expectedWorkingDir, tenantId: expectedTenantId, }); expect(context).toHaveProperty('requestContext'); } /** * Assert that an error contains specific data fields */ export function assertErrorData( error: McpError, expectedData: Record<string, unknown>, ): void { expect(error.data).toBeDefined(); expect(error.data).toMatchObject(expectedData); } /** * Assert that content is properly escaped/sanitized */ export function assertSanitizedContent(content: ContentBlock[]): void { expect(content).toHaveLength(1); const textContent = (content[0] as { type: 'text'; text: string }).text; // Check for common XSS patterns that should be escaped expect(textContent).not.toMatch(/<script>/i); expect(textContent).not.toMatch(/javascript:/i); expect(textContent).not.toMatch(/onerror=/i); } /** * Assert that response formatter output is LLM-friendly * Now supports both JSON and Markdown formats */ export function assertLlmFriendlyFormat( content: ContentBlock[], minLength = 50, ): void { expect(content).toHaveLength(1); const textContent = (content[0] as { type: 'text'; text: string }).text; // Should have meaningful content expect(textContent.length).toBeGreaterThan(minLength); // Check if it's JSON or Markdown const isJsonOnly = textContent.trim().startsWith('{') && textContent.trim().endsWith('}'); if (isJsonOnly) { // If it's JSON, it should be formatted (pretty-printed) expect(textContent).toMatch(/\n/); // Should be valid JSON expect(() => JSON.parse(textContent)).not.toThrow(); } else { // If it's Markdown, should have headers expect(textContent).toMatch(/^#\s+/m); } } /** * Assert that content blocks contain valid JSON with expected structure */ export function assertJsonContent( content: ContentBlock[], expectedStructure: Record<string, unknown>, ): void { expect(content).toHaveLength(1); expect(content[0]).toHaveProperty('type', 'text'); const textContent = (content[0] as { type: 'text'; text: string }).text; // Should be valid JSON let parsedJson: unknown; try { parsedJson = JSON.parse(textContent); } catch (error) { throw new Error(`Content is not valid JSON: ${textContent}`); } // Should match expected structure expect(parsedJson).toMatchObject(expectedStructure); } /** * Assert that content blocks contain valid JSON and return parsed object */ export function parseJsonContent(content: ContentBlock[]): unknown { expect(content).toHaveLength(1); expect(content[0]).toHaveProperty('type', 'text'); const textContent = (content[0] as { type: 'text'; text: string }).text; try { return JSON.parse(textContent); } catch (error) { throw new Error(`Content is not valid JSON: ${textContent}`); } } /** * Assert that JSON content has specific field with expected value */ export function assertJsonField( content: ContentBlock[], fieldPath: string, expectedValue: unknown, ): void { const parsed = parseJsonContent(content) as Record<string, unknown>; // Support nested paths like "status.current_branch" const pathParts = fieldPath.split('.'); let value: unknown = parsed; for (const part of pathParts) { if (value && typeof value === 'object' && part in value) { value = (value as Record<string, unknown>)[part]; } else { throw new Error(`Field path "${fieldPath}" not found in JSON`); } } if (typeof expectedValue === 'function') { // Allow for expect matchers like expect.any(Array) expect(value).toEqual(expectedValue); } else { expect(value).toEqual(expectedValue); } }

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/cyanheads/git-mcp-server'

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