Skip to main content
Glama

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

NameRequiredDescriptionDefault
petIdYesEnhanced pet ID description from overlay

Input Schema (JSON Schema)

{ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { "petId": { "description": "Enhanced pet ID description from overlay", "type": "integer" } }, "required": [ "petId" ], "type": "object" }

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; }

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