Skip to main content
Glama
response-handler.ts6.04 kB
import type { CallToolResult, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; import type { LogLayer } from 'loglayer'; import type { Operation } from 'oas/operation'; import type { McpifyOperation } from '../operation/ext.ts'; /** * Types of content that can be returned in a tool response */ type ContentType = 'text' | 'resource'; /** * Common structure for representing response content */ interface ResponseContentOptions { type: ContentType; uri: string; mimeType: string; content: string; } /** * Creates an error tool response */ async function createToolError( response: Response, log: LogLayer, operation: McpifyOperation, ): Promise<CallToolResult> { const errorText = await response.text(); log.warn(`Error response from ${operation.describe()}:`, errorText); return { isError: true, content: [ { type: 'text', text: errorText, }, ], }; } /** * Processes a response for tool calls. * * If the response is successful, it will be processed based on the operation's * response type. If the response is an error, it will be processed as text. * * @param response The successful response to process * @param log Logger instance * @param operation McpifyOperation instance * @returns A CallToolResult representing the successful response */ export async function handleToolResponse( response: Response, log: LogLayer, operation: McpifyOperation, ): Promise<CallToolResult> { if (response.status >= 400) { return createToolError(response, log, operation); } return toResponseContent(response, log, operation); } /** * Creates a resource error response */ function createResourceError(uri: string, mimeType: string, errorText: string): ReadResourceResult { return { isError: true, contents: [ { uri, mimeType, text: errorText, }, ], }; } /** * Creates a text resource response */ function createTextResource(uri: string, mimeType: string, text: string): ReadResourceResult { return { contents: [{ uri, mimeType, text }], }; } /** * Creates a binary resource response */ function createBinaryResource( uri: string, mimeType: string, data: ArrayBuffer, ): ReadResourceResult { const encoded = Buffer.from(data).toString('base64'); return { contents: [{ uri, mimeType, blob: encoded }], }; } /** * Processes a response for resource reading. * * If the response is successful, it will be processed based on the operation's * response type. If the response is an error, it will be processed as text. * * @param response The response to process * @param log Logger instance * @param operation McpifyOperation instance * @returns A ReadResourceResult representing the resource contents */ export async function handleResourceResponse( response: Response, log: LogLayer, operation: McpifyOperation, ): Promise<ReadResourceResult> { const uri = response.url; const mime = response.headers.get('Content-Type') ?? 'text/plain'; // Handle error responses if (response.status >= 400) { const errorText = await response.text(); log.warn(`Error response from ${operation.describe()}:`, errorText); return createResourceError(uri, mime, errorText); } // For successful responses, determine if text or binary if (isText(operation.inner)) { const text = await response.text(); return createTextResource(uri, mime, text); } else { const data = await response.arrayBuffer(); const binaryMime = mime === 'text/plain' ? 'application/octet-stream' : mime; return createBinaryResource(uri, binaryMime, data); } } /** * Determines if an operation returns text content */ export function isText(op: Operation): boolean { try { const schema = op.getResponseAsJSONSchema(200); // If schema exists and has 'binary' format, it's not text // Otherwise, return true (default to text) return schema.format !== 'binary'; } catch { // If we can't get the schema, default to text return true; } } /** * Creates a text content response */ function createTextContent(text: string): CallToolResult { return { content: [ { type: 'text', text, }, ], }; } /** * Creates a resource content response */ function createResourceContent(options: ResponseContentOptions): CallToolResult { if (options.type === 'resource') { return { content: [ { type: 'resource', resource: { uri: options.uri, mimeType: options.mimeType, text: options.content, }, }, ], }; } return createTextContent(options.content); } /** * Extracts and formats content from a response based on its type */ async function extractResponseContent(response: Response, mimeType: string): Promise<string> { switch (mimeType) { case 'application/json': { const json = await response.json(); return JSON.stringify(json); } case 'application/x-www-form-urlencoded': { const formData = await response.formData(); const search = new URLSearchParams(Object.entries(formData)); return String(search); } default: return await response.text(); } } /** * Converts a Response to a CallToolResult */ export async function toResponseContent( response: Response, log: LogLayer, ext: McpifyOperation, ): Promise<CallToolResult> { const content = await extractResponseContent(response, ext.responseType); // Log appropriately based on response type log.info(`Response from ${ext.describe()}:`, content); // Determine if the response should be a resource or text const isResourceType = ['application/json', 'application/x-www-form-urlencoded'].includes( ext.responseType, ); const options: ResponseContentOptions = { type: isResourceType ? 'resource' : 'text', uri: response.url, mimeType: ext.responseType, content, }; return createResourceContent(options); }

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/wycats/mcpify'

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