Skip to main content
Glama
TykTechnologies

OpenAPI to MCP Server

getPetById

Retrieve pet details by providing a unique pet ID using the MCP server generated from OpenAPI specifications for API integration with AI assistants.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
petIdYesEnhanced pet ID description from overlay

Implementation Reference

  • src/server.ts:48-165 (registration)
    Registers all dynamically mapped MCP tools from OpenAPI spec, including "getPetById". Creates Zod input schema from JSON Schema and registers server.tool with generic handler.
    // Register each tool with the server
    for (const tool of mappedTools) {
        const { mcpToolDefinition, apiCallDetails } = tool;
        console.error(`Registering MCP tool: ${mcpToolDefinition.name}`);  
        
        try {
            // Convert JSON Schema properties to zod schema
            const params: any = {};
            
            if (mcpToolDefinition.inputSchema && mcpToolDefinition.inputSchema.properties) {
                // Loop through all properties and create appropriate Zod schemas based on data type
                for (const [propName, propSchema] of Object.entries(mcpToolDefinition.inputSchema.properties)) {
                    if (typeof propSchema !== 'object') continue;
                    
                    const description = propSchema.description as string || `Parameter: ${propName}`;
                    const required = mcpToolDefinition.inputSchema.required?.includes(propName) || false;
                    
                    // Map JSON Schema types to Zod schema types
                    let zodSchema;
                    const schemaType = Array.isArray(propSchema.type) 
                        ? propSchema.type[0] // If type is an array (for nullable union types), use first type
                        : propSchema.type;
                        
                    // Handle different types with proper Zod schemas
                    switch (schemaType) {
                        case 'integer':
                            zodSchema = z.number().int().describe(description);
                            break;
                        case 'number':
                            zodSchema = z.number().describe(description);
                            break;
                        case 'boolean':
                            zodSchema = z.boolean().describe(description);
                            break;
                        case 'object':
                            // For objects, create a more permissive schema
                            zodSchema = z.object({}).passthrough().describe(description);
                            break;
                        case 'array':
                            // For arrays, allow any array content
                            zodSchema = z.array(z.any()).describe(description);
                            break;
                        case 'string':
                        default:
                            zodSchema = z.string().describe(description);
                            break;
                    }
                    
                    // Make it optional if not required
                    params[propName] = required ? zodSchema : zodSchema.optional();
                    
                    // Add this for debugging
                    console.error(`Registered parameter ${propName} with type: ${schemaType}, required: ${required}`);
                }
            }
            
            // Register the tool using proper MCP SDK format
            server.tool(
                mcpToolDefinition.name,
                params, // This schema will be visible in the MCP Inspector
                async (toolParams: any) => {
                    const requestId = 'req-' + Math.random().toString(36).substring(2, 9);
                    console.error(`MCP Tool '${mcpToolDefinition.name}' invoked. Request ID: ${requestId}`);
                    console.error(`Parameters received:`, toolParams);
                    
                    try {
                        // Execute the API call with the provided parameters
                        const result = await executeApiCall(apiCallDetails, toolParams);
                        
                        if (result.success) {
                            console.error(`[Request ID: ${requestId}] Tool '${mcpToolDefinition.name}' executed successfully.`);
                            
                            // Return success response
                            return {
                                content: [
                                    {
                                        type: "text", 
                                        text: JSON.stringify(result.data)
                                    }
                                ]
                            };
                        } else {
                            console.error(`[Request ID: ${requestId}] Tool '${mcpToolDefinition.name}' execution failed: ${result.error}`);
                            
                            // Map API errors to MCP errors
                            let errorCode = ErrorCode.InternalError;
                            let errorMessage = result.error || `API Error ${result.statusCode}`;
                            
                            if (result.statusCode === 400) {
                                errorCode = ErrorCode.InvalidParams;
                                errorMessage = `Invalid parameters: ${result.error}`;
                            } else if (result.statusCode === 404) {
                                errorCode = ErrorCode.InvalidParams;
                                errorMessage = `Resource not found: ${result.error}`;
                            }
                            
                            throw new McpError(errorCode, errorMessage, result.data);
                        }
                    } catch (invocationError: any) {
                        console.error(`[Request ID: ${requestId}] Error invoking tool:`, invocationError);
                        
                        if (invocationError instanceof McpError) {
                            throw invocationError; // Re-throw known MCP errors
                        }
                        
                        throw new McpError(
                            ErrorCode.InternalError, 
                            `Internal server error: ${invocationError.message}`
                        );
                    }
                }
            );
            
            console.error(`Registered Tool: ${mcpToolDefinition.name}`);
        } catch (registerError) {
            console.error(`Failed to register tool ${mcpToolDefinition.name}:`, registerError);
        }
    }
  • Handler executed for 'getPetById' tool invocation. Logs parameters, calls executeApiCall on the target API, returns JSON response or maps errors to MCP errors.
    async (toolParams: any) => {
        const requestId = 'req-' + Math.random().toString(36).substring(2, 9);
        console.error(`MCP Tool '${mcpToolDefinition.name}' invoked. Request ID: ${requestId}`);
        console.error(`Parameters received:`, toolParams);
        
        try {
            // Execute the API call with the provided parameters
            const result = await executeApiCall(apiCallDetails, toolParams);
            
            if (result.success) {
                console.error(`[Request ID: ${requestId}] Tool '${mcpToolDefinition.name}' executed successfully.`);
                
                // Return success response
                return {
                    content: [
                        {
                            type: "text", 
                            text: JSON.stringify(result.data)
                        }
                    ]
                };
            } else {
                console.error(`[Request ID: ${requestId}] Tool '${mcpToolDefinition.name}' execution failed: ${result.error}`);
                
                // Map API errors to MCP errors
                let errorCode = ErrorCode.InternalError;
                let errorMessage = result.error || `API Error ${result.statusCode}`;
                
                if (result.statusCode === 400) {
                    errorCode = ErrorCode.InvalidParams;
                    errorMessage = `Invalid parameters: ${result.error}`;
                } else if (result.statusCode === 404) {
                    errorCode = ErrorCode.InvalidParams;
                    errorMessage = `Resource not found: ${result.error}`;
                }
                
                throw new McpError(errorCode, errorMessage, result.data);
            }
        } catch (invocationError: any) {
            console.error(`[Request ID: ${requestId}] Error invoking tool:`, invocationError);
            
            if (invocationError instanceof McpError) {
                throw invocationError; // Re-throw known MCP errors
            }
            
            throw new McpError(
                ErrorCode.InternalError, 
                `Internal server error: ${invocationError.message}`
            );
        }
  • Helper function that executes the underlying HTTP API call for any tool, including 'getPetById'. Maps MCP parameters to path/query/header/body, applies auth/security, uses axios to call the target API.
    export async function executeApiCall(
        details: ApiCallDetails,
        mcpInput: Record<string, any> // The raw input object from MCP
    ): Promise<ApiClientResponse> {
        const { method, pathTemplate, serverUrl, parameters, requestBody, securityRequirements, securitySchemes } = details;
    
        // Input validation
        if (!method) {
            return { success: false, statusCode: 400, error: 'API call details missing HTTP method' };
        }
        
        if (!pathTemplate) {
            return { success: false, statusCode: 400, error: 'API call details missing path template' };
        }
        
        if (!serverUrl) {
            return { success: false, statusCode: 400, error: 'API call details missing server URL' };
        }
        
        // Ensure mcpInput is a valid object
        if (!mcpInput || typeof mcpInput !== 'object' || Array.isArray(mcpInput)) {
            console.error(`Invalid input type: ${typeof mcpInput}. Expected an object.`);
            return { success: false, statusCode: 400, error: 'Invalid input: expected an object' };
        }
    
        // Normalize the input object to handle different formats
        // Some MCP clients might send parameters differently
        const normalizedInput = { ...mcpInput };
    
        let url = `${serverUrl}${pathTemplate}`;
        const queryParams: Record<string, any> = {};
        const headers: Record<string, any> = {};
        let body: any = undefined;
    
        console.error(`Executing API call for tool: ${details.method} ${details.pathTemplate}`);
        console.error(`MCP Input received:`, normalizedInput);
    
    
        // Map MCP input back to HTTP request components
        for (const paramDef of parameters) {
            const paramName = paramDef.name;
            const paramValue = normalizedInput[paramName];
    
            if (paramValue !== undefined) { // Only map if present in MCP input
                // Validate parameter value according to schema if possible
                if (paramDef.schema) {
                    const validationError = validateParameterValue(paramValue, paramDef);
                    if (validationError) {
                        return { success: false, statusCode: 400, error: `Parameter '${paramName}': ${validationError}` };
                    }
                }
                
                switch (paramDef.in) {
                    case 'path':
                        url = url.replace(`{${paramName}}`, encodeURIComponent(String(paramValue)));
                        break;
                    case 'query':
                        queryParams[paramName] = paramValue;
                        break;
                    case 'header':
                        headers[paramName] = String(paramValue);
                        break;
                    case 'cookie':
                        // Cookie handling is more complex, often managed by agents or specific header logic
                        console.error(`Cookie parameter '${paramName}' handling not implemented.`);
                        break;
                }
            } else if (paramDef.required) {
                 console.error(`Error: Required parameter '${paramName}' missing in MCP input.`);
                 return { success: false, statusCode: 400, error: `Missing required parameter: ${paramName}` };
            }
        }
    
         // Map request body if defined and present in input
         if (requestBody && normalizedInput.requestBody !== undefined) {
             // Validate request body against schema if available
             const bodyValidationError = validateRequestBody(normalizedInput.requestBody, requestBody);
             if (bodyValidationError) {
                 return { success: false, statusCode: 400, error: `Request body validation failed: ${bodyValidationError}` };
             }
             
             // Assuming the nested 'requestBody' property in mcpInput holds the body
             body = normalizedInput.requestBody;
             // Assume application/json for now, get content type from requestBody definition if needed
             headers['Content-Type'] = 'application/json';
         } else if (requestBody?.required) {
              console.error(`Error: Required requestBody missing in MCP input.`);
              return { success: false, statusCode: 400, error: `Missing required request body` };
         }
    
    
        const requestConfig: AxiosRequestConfig = {
            method: method as any, // Cast needed for Axios types
            url: url,
            params: queryParams,
            headers: headers,
            data: body,
            // Validate status to handle non-2xx as resolved promises
            validateStatus: (status: number) => status >= 200 && status < 500, // Handle 4xx as well
        };
    
        // Apply custom headers from configuration
        if (config.customHeaders && Object.keys(config.customHeaders).length > 0) {
            requestConfig.headers = { ...requestConfig.headers, ...config.customHeaders };
        }
    
        // Add X-MCP header unless disabled
        if (!config.disableXMcp) {
            requestConfig.headers = { ...requestConfig.headers, 'X-MCP': '1' };
        }
        
        // Apply security before making the call
        try {
            await applySecurity(requestConfig, securityRequirements, securitySchemes);
        } catch (secErr: any) {
            console.error("Security application failed:", secErr);
            return { success: false, statusCode: 401, error: `Security setup failed: ${secErr.message}` };
        }
    
        console.error(`Making HTTP request:`, {
            method: requestConfig.method,
            url: requestConfig.url,
            params: requestConfig.params,
            headers: requestConfig.headers,
            data: requestConfig.data ? '[Request Body Present]' : undefined // Avoid logging sensitive data
        });
    
        try {
            const response = await axios(requestConfig);
    
            console.error(`API response received: Status ${response.status}`);
    
            if (response.status >= 200 && response.status < 300) {
                return {
                    success: true,
                    statusCode: response.status,
                    data: response.data,
                };
            } else {
                // Handle 4xx client errors reported by the API
                console.error(`API returned client error ${response.status}:`, response.data);
                return {
                    success: false,
                    statusCode: response.status,
                    error: `API Error ${response.status}: ${JSON.stringify(response.data)}`,
                    data: response.data // Optionally include error data
                };
            }
        } catch (error) {
            const axiosError = error as AxiosError;
            console.error(`API call failed: ${axiosError.message}`, axiosError.response?.data || axiosError.code);
    
            if (axiosError.response) {
                // Errors during the request setup or >= 500 if validateStatus wasn't broad enough
                return {
                    success: false,
                    statusCode: axiosError.response.status || 500,
                    error: `API Error ${axiosError.response.status}: ${JSON.stringify(axiosError.response.data) || axiosError.message}`,
                    data: axiosError.response.data
                };
            } else {
                // Network error, DNS error, etc.
                return {
                    success: false,
                    statusCode: 503, // Service Unavailable or similar
                    error: `Network or request setup error: ${axiosError.message}`,
                };
            }
        }
    }
  • Maps OpenAPI operations to MCP tools. Creates tool named 'getPetById' from matching operationId, generates inputSchema from parameters and outputSchema from 2xx response.
    export function mapOpenApiToMcpTools(openapi: ProcessedOpenAPI): MappedTool[] {
        const mappedTools: MappedTool[] = [];
        const globalSecurity = openapi.security || null; // Global security requirements
        const securitySchemes = openapi.components?.securitySchemes || undefined; // Security scheme definitions
    
        if (!openapi.paths) {
            console.error("OpenAPI spec has no paths defined.");
            return [];
        }
    
        // Determine the base server URL
        // Priority: Configured URL > First Server URL > Error/Default
        let baseServerUrl = config.targetApiBaseUrl;
        if (!baseServerUrl) {
            // Extract URL template from servers, defaulting to '/' if not found
            baseServerUrl = openapi.servers?.[0]?.url ?? '/';
        }
        // Ensure it's not undefined before using replace
        baseServerUrl = (baseServerUrl || '/').replace(/\/$/, '');
    
        for (const path in openapi.paths) {
            const pathItem = openapi.paths[path] as OpenAPIV3.PathItemObject; // Assuming dereferenced
    
            for (const method in pathItem) {
                // Check if the method is a valid HTTP method
                if (!['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes(method.toLowerCase())) {
                    continue;
                }
    
                const operation = pathItem[method as keyof OpenAPIV3.PathItemObject] as OpenAPIV3.OperationObject;
                if (!operation || typeof operation !== 'object') continue;
    
    
                const operationId = operation.operationId;
    
                // --- Filtering ---
                if (!shouldIncludeOperation(operationId, path, method)) {
                    // If operationId is available, log it for better debugging
                    if (operationId) {
                        console.error(`Skipping operation ${operationId} (${method.toUpperCase()} ${path}) due to filter rules.`);
                    } else {
                        console.error(`Skipping operation ${method.toUpperCase()} ${path} due to filter rules.`);
                    }
                    continue;
                }
    
                // Skip operations without operationId as we need it for the tool name
                if (!operationId) {
                    console.error(`Skipping operation ${method.toUpperCase()} ${path} due to missing operationId.`);
                    continue;
                }
    
                // --- Mapping ---
                let toolName = operationId;
                
                // Debug logging to identify what summary/description fields are available
                console.error(`Tool: ${toolName} - Operation description: ${operation.description || 'N/A'}`);
                console.error(`Tool: ${toolName} - Operation summary: ${operation.summary || 'N/A'}`);
                console.error(`Tool: ${toolName} - Path summary: ${pathItem.summary || 'N/A'}`);
                
                // Check for custom MCP extensions at the operation level first, then path level
                const operationMcpExtension = (operation as any)['x-mcp'];
                const pathMcpExtension = (pathItem as any)['x-mcp'];
                
                // Priority: Operation-level extension > Path-level extension > Default
                if (operationMcpExtension && typeof operationMcpExtension === 'object') {
                    if (operationMcpExtension.name && typeof operationMcpExtension.name === 'string') {
                        console.error(`Tool: ${toolName} - Using custom name from operation-level x-mcp extension: ${operationMcpExtension.name}`);
                        toolName = operationMcpExtension.name;
                    }
                } else if (pathMcpExtension && typeof pathMcpExtension === 'object') {
                    if (pathMcpExtension.name && typeof pathMcpExtension.name === 'string') {
                        console.error(`Tool: ${toolName} - Using custom name from path-level x-mcp extension: ${pathMcpExtension.name}`);
                        toolName = pathMcpExtension.name;
                    }
                }
    
                let toolDescription = operation.description || operation.summary || pathItem.summary || 'No description available.';
                
                // Check for custom description in MCP extension - operation level first, then path level
                if (operationMcpExtension && typeof operationMcpExtension === 'object') {
                    if (operationMcpExtension.description && typeof operationMcpExtension.description === 'string') {
                        console.error(`Tool: ${toolName} - Using custom description from operation-level x-mcp extension: ${operationMcpExtension.description}`);
                        toolDescription = operationMcpExtension.description;
                    }
                } else if (pathMcpExtension && typeof pathMcpExtension === 'object') {
                    if (pathMcpExtension.description && typeof pathMcpExtension.description === 'string') {
                        console.error(`Tool: ${toolName} - Using custom description from path-level x-mcp extension: ${pathMcpExtension.description}`);
                        toolDescription = pathMcpExtension.description;
                    }
                }
                
                console.error(`Tool: ${toolName} - Final description used: ${toolDescription}`);
    
                // --- Input Schema ---
                const inputJsonSchema: JSONSchema7 = {
                    type: 'object',
                    properties: {},
                    required: [],
                };
    
                const allParameters: OpenAPIV3.ParameterObject[] = [
                    ...(pathItem.parameters || []), // Parameters defined at path level
                    ...(operation.parameters || []), // Parameters defined at operation level
                ].filter(isParameterObject); // Ensure they are actual parameter objects
    
                // Group parameters by their location (path, query, header, cookie)
                const parametersByLocation: Record<string, OpenAPIV3.ParameterObject[]> = {};
                for (const param of allParameters) {
                    const location = param.in || 'query'; // Default to query if not specified
                    if (!parametersByLocation[location]) {
                        parametersByLocation[location] = [];
                    }
                    parametersByLocation[location].push(param);
                }
                
                // Create separate property for each parameter location group for better organization
                for (const [location, params] of Object.entries(parametersByLocation)) {
                    // If there are parameters in this location, create a property for them
                    if (params.length > 0 && inputJsonSchema.properties) {
                        // Process each parameter within its location group
                        for (const param of params) {
                            if (param.name && param.schema && inputJsonSchema.properties) {
                                // Debug log to identify potential type issues
                                console.error(`Processing parameter ${param.name} with schema type: ${(param.schema as any).type} format: ${(param.schema as any).format}`);
                                
                                // Convert OpenAPI schema to JSON Schema
                                const paramSchema = openApiSchemaToJsonSchema(param.schema as OpenAPIV3.SchemaObject);
                                
                                if (paramSchema) {
                                    // Debug log for converted schema
                                    console.error(`Converted schema for ${param.name}: type=${paramSchema.type}, format=${(paramSchema as any).format}`);
                                    
                                    // Add parameter to properties
                                    inputJsonSchema.properties[param.name] = paramSchema;
                                    
                                    // Add description if available
                                    if (param.description) {
                                        (inputJsonSchema.properties[param.name] as JSONSchema7).description = param.description;
                                    }
                                    
                                    // Add parameter location metadata as an annotation
                                    (inputJsonSchema.properties[param.name] as any)['x-parameter-location'] = location;
                                    
                                    // Add deprecated flag if needed
                                    if (param.deprecated) {
                                        (inputJsonSchema.properties[param.name] as any).deprecated = true;
                                    }
                                    
                                    // Add example if available
                                    if (param.example !== undefined) {
                                        (inputJsonSchema.properties[param.name] as any).example = param.example;
                                    }
                                    
                                    // Handle required parameters
                                    if (param.required && inputJsonSchema.required) {
                                        inputJsonSchema.required.push(param.name);
                                    }
                                    
                                    // Add any extensions (x-... properties)
                                    Object.keys(param).forEach(key => {
                                        if (key.startsWith('x-')) {
                                            (inputJsonSchema.properties![param.name] as any)[key] = param[key as keyof OpenAPIV3.ParameterObject];
                                        }
                                    });
                                }
                            }
                        }
                    }
                }
    
                // Handle Request Body
                if (isRequestBodyObject(operation.requestBody)) {
                    // Look for application/json content first, fall back to any available content type
                    const jsonContent = operation.requestBody.content?.['application/json']?.schema;
                    const anyContent = Object.values(operation.requestBody.content || {})[0]?.schema;
                    const requestBodySchema = jsonContent || anyContent;
    
                    if (isSchemaObject(requestBodySchema)) {
                        // Convert request body schema and add it to input schema
                        const convertedSchema = openApiSchemaToJsonSchema(requestBodySchema);
                        
                        if (convertedSchema && inputJsonSchema.properties) {
                            // Add as 'requestBody' property
                            inputJsonSchema.properties['requestBody'] = convertedSchema;
                            
                            // Mark as required if specified
                            if (operation.requestBody.required && inputJsonSchema.required) {
                                inputJsonSchema.required.push('requestBody');
                            }
                            
                            // Add description if available
                            if (operation.requestBody.description && inputJsonSchema.properties['requestBody']) {
                                inputJsonSchema.properties['requestBody'].description = operation.requestBody.description;
                            }
                            
                            // Add content type annotation
                            const contentTypes = Object.keys(operation.requestBody.content || {});
                            if (contentTypes.length > 0) {
                                (inputJsonSchema.properties['requestBody'] as any)['x-content-types'] = contentTypes;
                            }
                        }
                    }
                }
    
                // Remove empty required array if nothing is required
                if (inputJsonSchema.required?.length === 0) {
                    delete inputJsonSchema.required;
                }
    
                // --- Output Schema (Primary Success Response, e.g., 200) ---
                let outputJsonSchema: JSONSchema7 | undefined = undefined;
                const successResponseCode = Object.keys(operation.responses || {}).find(code => code.startsWith('2')); // Find first 2xx response
                if (successResponseCode) {
                    const response = operation.responses?.[successResponseCode];
                    if (isResponseObject(response)) {
                        const jsonContent = response.content?.['application/json']?.schema;
                        if (isSchemaObject(jsonContent)) {
                            const tempSchema = openApiSchemaToJsonSchema(jsonContent);
                            outputJsonSchema = tempSchema as JSONSchema7; // Cast to JSONSchema7 since we know it's a schema
                            if(outputJsonSchema && response.description){
                                outputJsonSchema.description = response.description; // Add response description
                            }
                        }
                    }
                }
    
                // --- Assemble MCP Tool Definition ---
                const mcpDefinition: McpToolDefinition = {
                    name: toolName,
                    description: toolDescription,
                    inputSchema: inputJsonSchema,
                    outputSchema: outputJsonSchema, // Properly include the outputSchema
                    annotations: {
                        // Add any relevant annotations, e.g., from spec extensions
                        'x-openapi-path': path,
                        'x-openapi-method': method.toUpperCase(),
                    }
                };
    
                // --- Assemble API Call Details ---
                const apiDetails: ApiCallDetails = {
                    method: method.toUpperCase(),
                    pathTemplate: path,
                    serverUrl: baseServerUrl, // Use the determined base URL
                    parameters: allParameters, // Store original params for mapping back
                    requestBody: isRequestBodyObject(operation.requestBody) ? operation.requestBody : undefined, // Store original body info
                    securityRequirements: operation.security !== undefined ? operation.security : globalSecurity, // Operation security overrides global
                    securitySchemes, // Include security schemes from OpenAPI components
                };
    
                mappedTools.push({ mcpToolDefinition: mcpDefinition, apiCallDetails: apiDetails });
                console.error(`Mapped tool: ${toolName} (${method.toUpperCase()} ${path})`);
            }
        }
    
        console.error(`Total tools mapped: ${mappedTools.length}`);
        return mappedTools;
    }
Install Server

Other Tools

Related Tools

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/TykTechnologies/api-to-mcp'

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