UNPKG

mcp-openapi-schema-explorer

Version:
199 lines 8 kB
import { createErrorResult, generateListHint } from './utils.js'; // Add .js export const VALID_COMPONENT_TYPES = [ 'schemas', 'responses', 'parameters', 'examples', 'requestBodies', 'headers', 'securitySchemes', 'links', 'callbacks', // 'pathItems' is technically allowed but we handle paths separately ]; // Simple descriptions for component types const componentTypeDescriptions = { schemas: 'Reusable data structures (models)', responses: 'Reusable API responses', parameters: 'Reusable request parameters (query, path, header, cookie)', examples: 'Reusable examples of media type payloads', requestBodies: 'Reusable request body definitions', headers: 'Reusable header definitions for responses', securitySchemes: 'Reusable security scheme definitions (e.g., API keys, OAuth2)', links: 'Reusable descriptions of links between responses and operations', callbacks: 'Reusable descriptions of callback operations', // pathItems: 'Reusable path item definitions (rarely used directly here)' // Excluded as per comment above }; // Use a Map for safer lookups against prototype pollution const componentDescriptionsMap = new Map(Object.entries(componentTypeDescriptions)); /** * Wraps an OpenAPIV3.ComponentsObject to make it renderable. * Handles listing the available component types. */ export class RenderableComponents { constructor(components) { this.components = components; } /** * Renders a list of available component types found in the spec. * Corresponds to the `openapi://components` URI. */ renderList(context) { if (!this.components || Object.keys(this.components).length === 0) { return createErrorResult('components', 'No components found in the specification.'); } const availableTypes = Object.keys(this.components).filter((key) => VALID_COMPONENT_TYPES.includes(key)); if (availableTypes.length === 0) { return createErrorResult('components', 'No valid component types found.'); } let listText = 'Available Component Types:\n\n'; availableTypes.sort().forEach(type => { const description = componentDescriptionsMap.get(type) ?? 'Unknown component type'; // Removed unnecessary 'as ComponentType' listText += `- ${type}: ${description}\n`; }); // Use the new hint generator structure, providing the first type as an example const firstTypeExample = availableTypes.length > 0 ? availableTypes[0] : undefined; listText += generateListHint(context, { itemType: 'componentType', firstItemExample: firstTypeExample, }); return [ { uriSuffix: 'components', data: listText, renderAsList: true, }, ]; } /** * Detail view for the main 'components' object isn't meaningful. */ renderDetail(context) { return this.renderList(context); } /** * Gets the map object for a specific component type. * @param type - The component type (e.g., 'schemas'). * @returns The map (e.g., ComponentsObject['schemas']) or undefined. */ getComponentMap(type) { // Use Map for safe access if (!this.components) { return undefined; } const componentsMap = new Map(Object.entries(this.components)); // Cast needed as Map.get returns the value type or undefined return componentsMap.get(type); } } // ===================================================================== /** * Wraps a map of components of a specific type (e.g., all schemas). * Handles listing component names and rendering component details. */ export class RenderableComponentMap { constructor(componentMap, componentType, // e.g., 'schemas' mapUriSuffix // e.g., 'components/schemas' ) { this.componentMap = componentMap; this.componentType = componentType; this.mapUriSuffix = mapUriSuffix; } /** * Renders a list of component names for the specific type. * Corresponds to the `openapi://components/{type}` URI. */ renderList(context) { if (!this.componentMap || Object.keys(this.componentMap).length === 0) { return createErrorResult(this.mapUriSuffix, `No components of type "${this.componentType}" found.`); } const names = Object.keys(this.componentMap).sort(); let listText = `Available ${this.componentType}:\n\n`; names.forEach(name => { listText += `- ${name}\n`; }); // Use the new hint generator structure, providing parent type and first name as example const firstNameExample = names.length > 0 ? names[0] : undefined; listText += generateListHint(context, { itemType: 'componentName', parentComponentType: this.componentType, firstItemExample: firstNameExample, }); return [ { uriSuffix: this.mapUriSuffix, data: listText, renderAsList: true, }, ]; } /** * Renders the detail view for one or more specific named components * Renders the detail view. For a component map, this usually means listing * the component names, similar to renderList. The handler should call * `renderComponentDetail` for specific component details. */ renderDetail(context) { // Delegate to renderList as the primary view for a component map itself. return this.renderList(context); } /** * Renders the detail view for one or more specific named components * within this map. * Corresponds to the `openapi://components/{type}/{name*}` URI. * This is called by the handler after identifying the name(s). * * @param _context - The rendering context (might be needed later). * @param names - Array of component names. * @returns An array of RenderResultItem representing the component details. */ renderComponentDetail(_context, names) { if (!this.componentMap) { // Create error results for all requested names if map is missing return names.map(name => ({ uriSuffix: `${this.mapUriSuffix}/${name}`, data: null, isError: true, errorText: `Component map for type "${this.componentType}" not found.`, renderAsList: true, })); } const results = []; for (const name of names) { const component = this.getComponent(name); const componentUriSuffix = `${this.mapUriSuffix}/${name}`; if (!component) { results.push({ uriSuffix: componentUriSuffix, data: null, isError: true, errorText: `Component "${name}" of type "${this.componentType}" not found.`, renderAsList: true, }); } else { // Return the raw component object; handler will format it results.push({ uriSuffix: componentUriSuffix, data: component, }); } } return results; } /** * Gets a specific component object by name. * @param name - The name of the component. * @returns The component object (or ReferenceObject) or undefined. */ getComponent(name) { // Use Map for safe access if (!this.componentMap) { return undefined; } const detailsMap = new Map(Object.entries(this.componentMap)); // No cast needed, Map.get returns the correct type (ValueType | undefined) return detailsMap.get(name); } } //# sourceMappingURL=components.js.map