Skip to main content
Glama
kadykov

OpenAPI Schema Explorer

spec-loading.test.ts6.71 kB
import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { ReadResourceResult, TextResourceContents } from '@modelcontextprotocol/sdk/types.js'; import { startMcpServer, McpTestContext } from '../../utils/mcp-test-helpers'; import { FormattedResultItem } from '../../../src/handlers/handler-utils'; import path from 'path'; // Helper function to parse JSON safely function parseJsonSafely(text: string | undefined): unknown { if (text === undefined) { throw new Error('Received undefined text for JSON parsing'); } try { return JSON.parse(text); } catch (e) { console.error('Failed to parse JSON:', text); throw new Error(`Invalid JSON received: ${e instanceof Error ? e.message : String(e)}`); } } // Type guard to check if content is TextResourceContents function hasTextContent( content: ReadResourceResult['contents'][0] ): content is TextResourceContents { return typeof (content as TextResourceContents).text === 'string'; } describe('E2E Tests for Spec Loading Scenarios', () => { let testContext: McpTestContext | null = null; // Allow null for cleanup let client: Client | null = null; // Allow null // Helper to setup client for tests, allowing different spec paths async function setup(specPathOrUrl: string): Promise<void> { // Cleanup previous context if exists if (testContext) { await testContext.cleanup(); testContext = null; client = null; } try { testContext = await startMcpServer(specPathOrUrl, { outputFormat: 'json' }); client = testContext.client; } catch (error) { // Explicitly convert error to string for logging const errorMsg = error instanceof Error ? error.message : String(error); console.warn(`Skipping tests for ${specPathOrUrl} due to setup error: ${errorMsg}`); testContext = null; // Ensure cleanup doesn't run on failed setup client = null; // Ensure tests are skipped } } afterEach(async () => { await testContext?.cleanup(); testContext = null; client = null; }); // Helper to read resource and perform basic checks async function readResourceAndCheck(uri: string): Promise<ReadResourceResult['contents'][0]> { if (!client) throw new Error('Client not initialized, skipping test.'); const result = await client.readResource({ uri }); expect(result.contents).toHaveLength(1); const content = result.contents[0]; expect(content.uri).toBe(uri); return content; } // Helper to read resource and check for text/plain list content async function checkTextListResponse(uri: string, expectedSubstrings: string[]): Promise<string> { const content = (await readResourceAndCheck(uri)) as FormattedResultItem; expect(content.mimeType).toBe('text/plain'); // expect(content.isError).toBeFalsy(); // Removed as SDK might strip this property if (!hasTextContent(content)) throw new Error('Expected text content'); for (const sub of expectedSubstrings) { expect(content.text).toContain(sub); } return content.text; } // Helper to read resource and check for JSON detail content async function checkJsonDetailResponse(uri: string, expectedObject: object): Promise<unknown> { const content = (await readResourceAndCheck(uri)) as FormattedResultItem; expect(content.mimeType).toBe('application/json'); // expect(content.isError).toBeFalsy(); // Removed as SDK might strip this property if (!hasTextContent(content)) throw new Error('Expected text content'); const data = parseJsonSafely(content.text); expect(data).toMatchObject(expectedObject); return data; } // --- Tests for Local Swagger v2.0 Spec --- describe('Local Swagger v2.0 Spec (sample-v2-api.json)', () => { const v2SpecPath = path.resolve(__dirname, '../../fixtures/sample-v2-api.json'); beforeAll(async () => await setup(v2SpecPath)); // Use beforeAll for this block it('should retrieve the converted "info" field', async () => { if (!client) return; // Skip if setup failed await checkJsonDetailResponse('openapi://info', { title: 'Simple Swagger 2.0 API', version: '1.0.0', }); }); it('should retrieve the converted "paths" list', async () => { if (!client) return; // Skip if setup failed await checkTextListResponse('openapi://paths', [ 'Hint:', 'GET /v2/ping', // Note the basePath is included ]); }); it('should retrieve the converted "components" list', async () => { if (!client) return; // Skip if setup failed await checkTextListResponse('openapi://components', [ 'Available Component Types:', '- schemas', "Hint: Use 'openapi://components/{type}'", ]); }); it('should get details for converted schema Pong', async () => { if (!client) return; // Skip if setup failed await checkJsonDetailResponse('openapi://components/schemas/Pong', { type: 'object', properties: { message: { type: 'string', example: 'pong' } }, }); }); }); // --- Tests for Remote OpenAPI v3.0 Spec (Petstore) --- // Increase timeout for remote fetch jest.setTimeout(20000); // 20 seconds describe('Remote OpenAPI v3.0 Spec (Petstore)', () => { const petstoreUrl = 'https://petstore3.swagger.io/api/v3/openapi.json'; beforeAll(async () => await setup(petstoreUrl)); // Use beforeAll for this block it('should retrieve the "info" field from Petstore', async () => { if (!client) return; // Skip if setup failed await checkJsonDetailResponse('openapi://info', { title: 'Swagger Petstore - OpenAPI 3.0', // version might change, so don't assert exact value }); }); it('should retrieve the "paths" list from Petstore', async () => { if (!client) return; // Skip if setup failed // Check for a known path await checkTextListResponse('openapi://paths', ['/pet/{petId}']); }); it('should retrieve the "components" list from Petstore', async () => { if (!client) return; // Skip if setup failed // Check for known component types await checkTextListResponse('openapi://components', [ '- schemas', '- requestBodies', '- securitySchemes', ]); }); it('should get details for schema Pet from Petstore', async () => { if (!client) return; // Skip if setup failed await checkJsonDetailResponse('openapi://components/schemas/Pet', { required: ['name', 'photoUrls'], type: 'object', // Check a known property properties: { id: { type: 'integer', format: 'int64' } }, }); }); }); });

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/kadykov/mcp-openapi-schema-explorer'

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