mcp-openapi-schema-explorer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.devcontainer/devcontainer.json +24 -0
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/ci.yml +111 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +12 -0
- package/.releaserc.json +23 -0
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +67 -0
- package/Dockerfile +3 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +19 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/handlers/component-detail-handler.d.ts +14 -0
- package/dist/src/handlers/component-detail-handler.js +87 -0
- package/dist/src/handlers/component-detail-handler.js.map +1 -0
- package/dist/src/handlers/component-map-handler.d.ts +14 -0
- package/dist/src/handlers/component-map-handler.js +63 -0
- package/dist/src/handlers/component-map-handler.js.map +1 -0
- package/dist/src/handlers/handler-utils.d.ts +69 -0
- package/dist/src/handlers/handler-utils.js +180 -0
- package/dist/src/handlers/handler-utils.js.map +1 -0
- package/dist/src/handlers/operation-handler.d.ts +14 -0
- package/dist/src/handlers/operation-handler.js +86 -0
- package/dist/src/handlers/operation-handler.js.map +1 -0
- package/dist/src/handlers/path-item-handler.d.ts +14 -0
- package/dist/src/handlers/path-item-handler.js +66 -0
- package/dist/src/handlers/path-item-handler.js.map +1 -0
- package/dist/src/handlers/top-level-field-handler.d.ts +14 -0
- package/dist/src/handlers/top-level-field-handler.js +72 -0
- package/dist/src/handlers/top-level-field-handler.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +177 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/rendering/components.d.ts +67 -0
- package/dist/src/rendering/components.js +177 -0
- package/dist/src/rendering/components.js.map +1 -0
- package/dist/src/rendering/document.d.ts +36 -0
- package/dist/src/rendering/document.js +147 -0
- package/dist/src/rendering/document.js.map +1 -0
- package/dist/src/rendering/path-item.d.ts +45 -0
- package/dist/src/rendering/path-item.js +141 -0
- package/dist/src/rendering/path-item.js.map +1 -0
- package/dist/src/rendering/paths.d.ts +26 -0
- package/dist/src/rendering/paths.js +78 -0
- package/dist/src/rendering/paths.js.map +1 -0
- package/dist/src/rendering/types.d.ts +50 -0
- package/dist/src/rendering/types.js +12 -0
- package/dist/src/rendering/types.js.map +1 -0
- package/dist/src/rendering/utils.d.ts +31 -0
- package/dist/src/rendering/utils.js +79 -0
- package/dist/src/rendering/utils.js.map +1 -0
- package/dist/src/services/formatters.d.ts +36 -0
- package/dist/src/services/formatters.js +52 -0
- package/dist/src/services/formatters.js.map +1 -0
- package/dist/src/services/reference-transform.d.ts +27 -0
- package/dist/src/services/reference-transform.js +75 -0
- package/dist/src/services/reference-transform.js.map +1 -0
- package/dist/src/services/spec-loader.d.ts +27 -0
- package/dist/src/services/spec-loader.js +77 -0
- package/dist/src/services/spec-loader.js.map +1 -0
- package/dist/src/types.d.ts +11 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/uri-builder.d.ts +81 -0
- package/dist/src/utils/uri-builder.js +121 -0
- package/dist/src/utils/uri-builder.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +4 -0
- package/dist/src/version.js.map +1 -0
- package/eslint.config.js +88 -0
- package/jest.config.js +32 -0
- package/justfile +66 -0
- package/memory-bank/activeContext.md +139 -0
- package/memory-bank/productContext.md +39 -0
- package/memory-bank/progress.md +141 -0
- package/memory-bank/projectbrief.md +50 -0
- package/memory-bank/systemPatterns.md +224 -0
- package/memory-bank/techContext.md +131 -0
- package/package.json +76 -0
- package/scripts/generate-version.js +49 -0
- package/src/config.ts +33 -0
- package/src/handlers/component-detail-handler.ts +121 -0
- package/src/handlers/component-map-handler.ts +92 -0
- package/src/handlers/handler-utils.ts +230 -0
- package/src/handlers/operation-handler.ts +114 -0
- package/src/handlers/path-item-handler.ts +88 -0
- package/src/handlers/top-level-field-handler.ts +92 -0
- package/src/index.ts +222 -0
- package/src/rendering/components.ts +228 -0
- package/src/rendering/document.ts +167 -0
- package/src/rendering/path-item.ts +157 -0
- package/src/rendering/paths.ts +87 -0
- package/src/rendering/types.ts +63 -0
- package/src/rendering/utils.ts +107 -0
- package/src/services/formatters.ts +71 -0
- package/src/services/reference-transform.ts +105 -0
- package/src/services/spec-loader.ts +88 -0
- package/src/types.ts +17 -0
- package/src/utils/uri-builder.ts +134 -0
- package/src/version.ts +4 -0
- package/test/__tests__/e2e/format.test.ts +224 -0
- package/test/__tests__/e2e/resources.test.ts +369 -0
- package/test/__tests__/e2e/spec-loading.test.ts +172 -0
- package/test/__tests__/unit/config.test.ts +39 -0
- package/test/__tests__/unit/handlers/component-detail-handler.test.ts +241 -0
- package/test/__tests__/unit/handlers/component-map-handler.test.ts +187 -0
- package/test/__tests__/unit/handlers/handler-utils.test.ts +255 -0
- package/test/__tests__/unit/handlers/operation-handler.test.ts +202 -0
- package/test/__tests__/unit/handlers/path-item-handler.test.ts +153 -0
- package/test/__tests__/unit/handlers/top-level-field-handler.test.ts +182 -0
- package/test/__tests__/unit/rendering/components.test.ts +269 -0
- package/test/__tests__/unit/rendering/document.test.ts +172 -0
- package/test/__tests__/unit/rendering/path-item.test.ts +197 -0
- package/test/__tests__/unit/rendering/paths.test.ts +115 -0
- package/test/__tests__/unit/services/formatters.test.ts +109 -0
- package/test/__tests__/unit/services/reference-transform.test.ts +320 -0
- package/test/__tests__/unit/services/spec-loader.test.ts +214 -0
- package/test/__tests__/unit/utils/uri-builder.test.ts +103 -0
- package/test/fixtures/complex-endpoint.json +146 -0
- package/test/fixtures/empty-api.json +8 -0
- package/test/fixtures/multi-component-types.json +55 -0
- package/test/fixtures/paths-test.json +61 -0
- package/test/fixtures/sample-api.json +68 -0
- package/test/fixtures/sample-v2-api.json +39 -0
- package/test/setup.ts +32 -0
- package/test/utils/console-helpers.ts +48 -0
- package/test/utils/mcp-test-helpers.ts +66 -0
- package/test/utils/test-types.ts +54 -0
- package/tsconfig.json +25 -0
- package/tsconfig.test.json +5 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OpenAPI } from 'openapi-types';
|
|
2
|
+
import type { TransformContext } from './services/reference-transform.js';
|
|
3
|
+
|
|
4
|
+
/** Common HTTP methods used in OpenAPI specs */
|
|
5
|
+
export type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'patch';
|
|
6
|
+
|
|
7
|
+
/** Interface for spec loader */
|
|
8
|
+
export interface SpecLoaderService {
|
|
9
|
+
getSpec(): Promise<OpenAPI.Document>;
|
|
10
|
+
getTransformedSpec(context: TransformContext): Promise<OpenAPI.Document>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Re-export transform types
|
|
14
|
+
export type { TransformContext };
|
|
15
|
+
|
|
16
|
+
// Re-export OpenAPI types
|
|
17
|
+
export type { OpenAPI };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for building standardized MCP URIs for this server.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const BASE_URI_SCHEME = 'openapi://';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Encodes a string component for safe inclusion in a URI path segment.
|
|
9
|
+
* Uses standard encodeURIComponent.
|
|
10
|
+
* Encodes a path string for safe inclusion in a URI.
|
|
11
|
+
* This specifically targets path strings which might contain characters
|
|
12
|
+
* like '{', '}', etc., that need encoding when forming the URI path part.
|
|
13
|
+
* Uses standard encodeURIComponent.
|
|
14
|
+
* Encodes a path string for safe inclusion in a URI path segment.
|
|
15
|
+
* This is necessary because the path segment comes from the user potentially
|
|
16
|
+
* containing characters that need encoding (like '{', '}').
|
|
17
|
+
* Uses standard encodeURIComponent.
|
|
18
|
+
* @param path The path string to encode.
|
|
19
|
+
* @returns The encoded path string, with leading slashes removed before encoding.
|
|
20
|
+
*/
|
|
21
|
+
export function encodeUriPathComponent(path: string): string {
|
|
22
|
+
// Added export
|
|
23
|
+
// Remove leading slashes before encoding
|
|
24
|
+
const pathWithoutLeadingSlash = path.replace(/^\/+/, '');
|
|
25
|
+
return encodeURIComponent(pathWithoutLeadingSlash);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// --- Full URI Builders ---
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Builds the URI for accessing a specific component's details.
|
|
32
|
+
* Example: openapi://components/schemas/MySchema
|
|
33
|
+
* @param type The component type (e.g., 'schemas', 'responses').
|
|
34
|
+
* @param name The component name.
|
|
35
|
+
* @returns The full component detail URI.
|
|
36
|
+
*/
|
|
37
|
+
export function buildComponentDetailUri(type: string, name: string): string {
|
|
38
|
+
// Per user instruction, do not encode type or name here.
|
|
39
|
+
return `${BASE_URI_SCHEME}components/${type}/${name}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds the URI for listing components of a specific type.
|
|
44
|
+
* Example: openapi://components/schemas
|
|
45
|
+
* @param type The component type (e.g., 'schemas', 'responses').
|
|
46
|
+
* @returns The full component map URI.
|
|
47
|
+
*/
|
|
48
|
+
export function buildComponentMapUri(type: string): string {
|
|
49
|
+
// Per user instruction, do not encode type here.
|
|
50
|
+
return `${BASE_URI_SCHEME}components/${type}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Builds the URI for accessing a specific operation's details.
|
|
55
|
+
* Example: openapi://paths/users/{userId}/GET
|
|
56
|
+
* @param path The API path (e.g., '/users/{userId}').
|
|
57
|
+
* @param method The HTTP method (e.g., 'GET', 'POST').
|
|
58
|
+
* @returns The full operation detail URI.
|
|
59
|
+
*/
|
|
60
|
+
export function buildOperationUri(path: string, method: string): string {
|
|
61
|
+
// Encode only the path component. Assume 'path' is raw/decoded.
|
|
62
|
+
// Method is assumed to be safe or handled by SDK/client.
|
|
63
|
+
return `${BASE_URI_SCHEME}paths/${encodeUriPathComponent(path)}/${method.toLowerCase()}`; // Standardize method to lowercase
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Builds the URI for listing methods available at a specific path.
|
|
68
|
+
* Example: openapi://paths/users/{userId}
|
|
69
|
+
* @param path The API path (e.g., '/users/{userId}').
|
|
70
|
+
* @returns The full path item URI.
|
|
71
|
+
*/
|
|
72
|
+
export function buildPathItemUri(path: string): string {
|
|
73
|
+
// Encode only the path component. Assume 'path' is raw/decoded.
|
|
74
|
+
return `${BASE_URI_SCHEME}paths/${encodeUriPathComponent(path)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Builds the URI for accessing a top-level field (like 'info' or 'servers')
|
|
79
|
+
* or triggering a list view ('paths', 'components').
|
|
80
|
+
* Example: openapi://info, openapi://paths
|
|
81
|
+
* @param field The top-level field name.
|
|
82
|
+
* @returns The full top-level field URI.
|
|
83
|
+
*/
|
|
84
|
+
export function buildTopLevelFieldUri(field: string): string {
|
|
85
|
+
// Per user instruction, do not encode field here.
|
|
86
|
+
return `${BASE_URI_SCHEME}${field}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- URI Suffix Builders (for RenderResultItem) ---
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Builds the URI suffix for a specific component's details.
|
|
93
|
+
* Example: components/schemas/MySchema
|
|
94
|
+
*/
|
|
95
|
+
export function buildComponentDetailUriSuffix(type: string, name: string): string {
|
|
96
|
+
// Per user instruction, do not encode type or name here.
|
|
97
|
+
return `components/${type}/${name}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Builds the URI suffix for listing components of a specific type.
|
|
102
|
+
* Example: components/schemas
|
|
103
|
+
*/
|
|
104
|
+
export function buildComponentMapUriSuffix(type: string): string {
|
|
105
|
+
// Per user instruction, do not encode type here.
|
|
106
|
+
return `components/${type}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Builds the URI suffix for a specific operation's details.
|
|
111
|
+
* Example: paths/users/{userId}/get
|
|
112
|
+
*/
|
|
113
|
+
export function buildOperationUriSuffix(path: string, method: string): string {
|
|
114
|
+
// Encode only the path component for the suffix. Assume 'path' is raw/decoded.
|
|
115
|
+
return `paths/${encodeUriPathComponent(path)}/${method.toLowerCase()}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Builds the URI suffix for listing methods available at a specific path.
|
|
120
|
+
* Example: paths/users/{userId}
|
|
121
|
+
*/
|
|
122
|
+
export function buildPathItemUriSuffix(path: string): string {
|
|
123
|
+
// Encode only the path component for the suffix. Assume 'path' is raw/decoded.
|
|
124
|
+
return `paths/${encodeUriPathComponent(path)}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Builds the URI suffix for a top-level field.
|
|
129
|
+
* Example: info, paths
|
|
130
|
+
*/
|
|
131
|
+
export function buildTopLevelFieldUriSuffix(field: string): string {
|
|
132
|
+
// Per user instruction, do not encode field here.
|
|
133
|
+
return field;
|
|
134
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { startMcpServer, McpTestContext } from '../../utils/mcp-test-helpers.js'; // Import McpTestContext
|
|
3
|
+
import { load as yamlLoad } from 'js-yaml';
|
|
4
|
+
// Remove old test types/guards if not needed, or adapt them
|
|
5
|
+
// import { isEndpointErrorResponse } from '../../utils/test-types.js';
|
|
6
|
+
// import type { EndpointResponse, ResourceResponse } from '../../utils/test-types.js';
|
|
7
|
+
// Import specific SDK types needed
|
|
8
|
+
import { ReadResourceResult, TextResourceContents } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
// Generic type guard for simple object check
|
|
10
|
+
function isObject(obj: unknown): obj is Record<string, unknown> {
|
|
11
|
+
return typeof obj === 'object' && obj !== null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Type guard to check if content is TextResourceContents
|
|
15
|
+
function hasTextContent(
|
|
16
|
+
content: ReadResourceResult['contents'][0]
|
|
17
|
+
): content is TextResourceContents {
|
|
18
|
+
// Check for the 'text' property specifically and ensure it's not undefined
|
|
19
|
+
return content && typeof (content as TextResourceContents).text === 'string';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseJson(text: string | undefined): unknown {
|
|
23
|
+
if (text === undefined) throw new Error('Cannot parse undefined text');
|
|
24
|
+
return JSON.parse(text);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseYaml(text: string | undefined): unknown {
|
|
28
|
+
if (text === undefined) throw new Error('Cannot parse undefined text');
|
|
29
|
+
const result = yamlLoad(text);
|
|
30
|
+
if (result === undefined) {
|
|
31
|
+
throw new Error('Invalid YAML: parsing resulted in undefined');
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function safeParse(text: string | undefined, format: 'json' | 'yaml'): unknown {
|
|
37
|
+
try {
|
|
38
|
+
return format === 'json' ? parseJson(text) : parseYaml(text);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Failed to parse ${format} content: ${error instanceof Error ? error.message : String(error)}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Removed old parseEndpointResponse
|
|
47
|
+
|
|
48
|
+
describe('Output Format E2E', () => {
|
|
49
|
+
let testContext: McpTestContext;
|
|
50
|
+
let client: Client;
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
await testContext?.cleanup();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('JSON format (default)', () => {
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
testContext = await startMcpServer('test/fixtures/complex-endpoint.json', {
|
|
59
|
+
outputFormat: 'json',
|
|
60
|
+
});
|
|
61
|
+
client = testContext.client;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return JSON for openapi://info', async () => {
|
|
65
|
+
const result = await client.readResource({ uri: 'openapi://info' });
|
|
66
|
+
expect(result.contents).toHaveLength(1);
|
|
67
|
+
const content = result.contents[0];
|
|
68
|
+
expect(content.mimeType).toBe('application/json');
|
|
69
|
+
if (!hasTextContent(content)) throw new Error('Expected text content'); // Add guard
|
|
70
|
+
expect(() => safeParse(content.text, 'json')).not.toThrow();
|
|
71
|
+
const data = safeParse(content.text, 'json');
|
|
72
|
+
expect(isObject(data) && data['title']).toBe('Complex Endpoint Test API'); // Use bracket notation after guard
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return JSON for operation detail', async () => {
|
|
76
|
+
const path = encodeURIComponent('api/v1/organizations/{orgId}/projects/{projectId}/tasks');
|
|
77
|
+
const result = await client.readResource({ uri: `openapi://paths/${path}/get` });
|
|
78
|
+
expect(result.contents).toHaveLength(1);
|
|
79
|
+
const content = result.contents[0];
|
|
80
|
+
expect(content.mimeType).toBe('application/json');
|
|
81
|
+
if (!hasTextContent(content)) throw new Error('Expected text content'); // Add guard
|
|
82
|
+
expect(() => safeParse(content.text, 'json')).not.toThrow();
|
|
83
|
+
const data = safeParse(content.text, 'json');
|
|
84
|
+
expect(isObject(data) && data['operationId']).toBe('getProjectTasks'); // Use bracket notation after guard
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should return JSON for component detail', async () => {
|
|
88
|
+
const result = await client.readResource({ uri: 'openapi://components/schemas/Task' });
|
|
89
|
+
expect(result.contents).toHaveLength(1);
|
|
90
|
+
const content = result.contents[0];
|
|
91
|
+
expect(content.mimeType).toBe('application/json');
|
|
92
|
+
if (!hasTextContent(content)) throw new Error('Expected text content'); // Add guard
|
|
93
|
+
expect(() => safeParse(content.text, 'json')).not.toThrow();
|
|
94
|
+
const data = safeParse(content.text, 'json');
|
|
95
|
+
expect(isObject(data) && data['type']).toBe('object'); // Use bracket notation after guard
|
|
96
|
+
expect(
|
|
97
|
+
isObject(data) &&
|
|
98
|
+
isObject(data['properties']) &&
|
|
99
|
+
isObject(data['properties']['id']) &&
|
|
100
|
+
data['properties']['id']['type']
|
|
101
|
+
).toBe('string'); // Use bracket notation with type checking
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('YAML format', () => {
|
|
106
|
+
beforeEach(async () => {
|
|
107
|
+
testContext = await startMcpServer('test/fixtures/complex-endpoint.json', {
|
|
108
|
+
outputFormat: 'yaml',
|
|
109
|
+
});
|
|
110
|
+
client = testContext.client;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return YAML for openapi://info', async () => {
|
|
114
|
+
const result = await client.readResource({ uri: 'openapi://info' });
|
|
115
|
+
expect(result.contents).toHaveLength(1);
|
|
116
|
+
const content = result.contents[0];
|
|
117
|
+
expect(content.mimeType).toBe('text/yaml');
|
|
118
|
+
if (!hasTextContent(content)) throw new Error('Expected text content'); // Add guard
|
|
119
|
+
expect(() => safeParse(content.text, 'yaml')).not.toThrow();
|
|
120
|
+
expect(content.text).toContain('title: Complex Endpoint Test API');
|
|
121
|
+
expect(content.text).toMatch(/\n$/);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should return YAML for operation detail', async () => {
|
|
125
|
+
const path = encodeURIComponent('api/v1/organizations/{orgId}/projects/{projectId}/tasks');
|
|
126
|
+
const result = await client.readResource({ uri: `openapi://paths/${path}/get` });
|
|
127
|
+
expect(result.contents).toHaveLength(1);
|
|
128
|
+
const content = result.contents[0];
|
|
129
|
+
expect(content.mimeType).toBe('text/yaml');
|
|
130
|
+
if (!hasTextContent(content)) throw new Error('Expected text content'); // Add guard
|
|
131
|
+
expect(() => safeParse(content.text, 'yaml')).not.toThrow();
|
|
132
|
+
expect(content.text).toContain('operationId: getProjectTasks');
|
|
133
|
+
expect(content.text).toMatch(/\n$/);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should return YAML for component detail', async () => {
|
|
137
|
+
const result = await client.readResource({ uri: 'openapi://components/schemas/Task' });
|
|
138
|
+
expect(result.contents).toHaveLength(1);
|
|
139
|
+
const content = result.contents[0];
|
|
140
|
+
expect(content.mimeType).toBe('text/yaml');
|
|
141
|
+
if (!hasTextContent(content)) throw new Error('Expected text content'); // Add guard
|
|
142
|
+
expect(() => safeParse(content.text, 'yaml')).not.toThrow();
|
|
143
|
+
expect(content.text).toContain('type: object');
|
|
144
|
+
expect(content.text).toContain('properties:');
|
|
145
|
+
expect(content.text).toContain('id:');
|
|
146
|
+
expect(content.text).toMatch(/\n$/);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Note: The test for listResourceTemplates is removed as it tested old template structure.
|
|
150
|
+
// We could add a new test here if needed, but the mimeType for templates isn't explicitly set anymore.
|
|
151
|
+
|
|
152
|
+
it('should handle errors in YAML format (e.g., invalid component name)', async () => {
|
|
153
|
+
const result = await client.readResource({ uri: 'openapi://components/schemas/InvalidName' });
|
|
154
|
+
expect(result.contents).toHaveLength(1);
|
|
155
|
+
const content = result.contents[0];
|
|
156
|
+
// Errors are always text/plain, regardless of configured output format
|
|
157
|
+
expect(content.mimeType).toBe('text/plain');
|
|
158
|
+
expect(content.isError).toBe(true);
|
|
159
|
+
if (!hasTextContent(content)) throw new Error('Expected text');
|
|
160
|
+
// Updated error message from getValidatedComponentDetails with sorted names
|
|
161
|
+
expect(content.text).toContain(
|
|
162
|
+
'None of the requested names (InvalidName) are valid for component type "schemas". Available names: CreateTaskRequest, Task, TaskList'
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Minified JSON format', () => {
|
|
168
|
+
beforeEach(async () => {
|
|
169
|
+
testContext = await startMcpServer('test/fixtures/complex-endpoint.json', {
|
|
170
|
+
outputFormat: 'json-minified',
|
|
171
|
+
});
|
|
172
|
+
client = testContext.client;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should return minified JSON for openapi://info', async () => {
|
|
176
|
+
const result = await client.readResource({ uri: 'openapi://info' });
|
|
177
|
+
expect(result.contents).toHaveLength(1);
|
|
178
|
+
const content = result.contents[0];
|
|
179
|
+
expect(content.mimeType).toBe('application/json');
|
|
180
|
+
if (!hasTextContent(content)) throw new Error('Expected text content');
|
|
181
|
+
expect(() => safeParse(content.text, 'json')).not.toThrow();
|
|
182
|
+
const data = safeParse(content.text, 'json');
|
|
183
|
+
expect(isObject(data) && data['title']).toBe('Complex Endpoint Test API');
|
|
184
|
+
// Check for lack of pretty-printing whitespace
|
|
185
|
+
expect(content.text).not.toContain('\n ');
|
|
186
|
+
expect(content.text).not.toContain(' '); // Double check no indentation
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should return minified JSON for operation detail', async () => {
|
|
190
|
+
const path = encodeURIComponent('api/v1/organizations/{orgId}/projects/{projectId}/tasks');
|
|
191
|
+
const result = await client.readResource({ uri: `openapi://paths/${path}/get` });
|
|
192
|
+
expect(result.contents).toHaveLength(1);
|
|
193
|
+
const content = result.contents[0];
|
|
194
|
+
expect(content.mimeType).toBe('application/json');
|
|
195
|
+
if (!hasTextContent(content)) throw new Error('Expected text content');
|
|
196
|
+
expect(() => safeParse(content.text, 'json')).not.toThrow();
|
|
197
|
+
const data = safeParse(content.text, 'json');
|
|
198
|
+
expect(isObject(data) && data['operationId']).toBe('getProjectTasks');
|
|
199
|
+
// Check for lack of pretty-printing whitespace
|
|
200
|
+
expect(content.text).not.toContain('\n ');
|
|
201
|
+
expect(content.text).not.toContain(' ');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should return minified JSON for component detail', async () => {
|
|
205
|
+
const result = await client.readResource({ uri: 'openapi://components/schemas/Task' });
|
|
206
|
+
expect(result.contents).toHaveLength(1);
|
|
207
|
+
const content = result.contents[0];
|
|
208
|
+
expect(content.mimeType).toBe('application/json');
|
|
209
|
+
if (!hasTextContent(content)) throw new Error('Expected text content');
|
|
210
|
+
expect(() => safeParse(content.text, 'json')).not.toThrow();
|
|
211
|
+
const data = safeParse(content.text, 'json');
|
|
212
|
+
expect(isObject(data) && data['type']).toBe('object');
|
|
213
|
+
expect(
|
|
214
|
+
isObject(data) &&
|
|
215
|
+
isObject(data['properties']) &&
|
|
216
|
+
isObject(data['properties']['id']) &&
|
|
217
|
+
data['properties']['id']['type']
|
|
218
|
+
).toBe('string');
|
|
219
|
+
// Check for lack of pretty-printing whitespace
|
|
220
|
+
expect(content.text).not.toContain('\n ');
|
|
221
|
+
expect(content.text).not.toContain(' ');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|