Skip to main content
Glama
base.ts9.99 kB
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; export interface ParameterDescriptor { name: string; type: string; description: string; defaultValue?: unknown; values?: string[]; } export interface ToolOperationDescriptor { name: string; summary: string; required: ParameterDescriptor[]; optional: ParameterDescriptor[]; remarks?: string[]; } export interface ToolCapabilityDescriptor { tool: string; summary: string; operations?: ToolOperationDescriptor[]; contexts?: string[]; requiresApiKey?: boolean; stageable?: boolean; tokenProfile?: { typical: number; upper?: number; }; metadata?: Record<string, unknown>; } export interface ToolContext { server: McpServer; baseUrl: string; defaultEmail: string; defaultTool: string; getApiKey(): string | undefined; getEnvironment(): Env | undefined; isValidDatabase(db: string): boolean; // biome-ignore lint/suspicious/noExplicitAny: shared context needs to accommodate heterogeneous tool responses parseResponse( response: Response, toolName: string, requestedRetmode?: string, ): Promise<string | any>; // biome-ignore lint/suspicious/noExplicitAny: shared context needs to accommodate heterogeneous tool responses formatResponseData(data: any): string; buildUrl(endpoint: string, params: URLSearchParams): string; // biome-ignore lint/suspicious/noExplicitAny: shared context needs to accommodate heterogeneous tool responses shouldBypassStaging( processedData: any[], diagnostics: any, payloadSize: number, ): { bypass: boolean; reason: string }; getOptimalRetmode( tool: string, database: string, intendedUse?: string, ): string; shouldStageResponse( data: string, toolName: string, ): { shouldStage: boolean; reason: string; estimatedTokens: number }; validateQuery( query: string, database: string, ): { valid: boolean; message?: string; suggestion?: string }; suggestQueryImprovements(query: string, database: string): string[]; } // MCP 2025-11-25 Specification-compliant content types /** * Annotations for content items per MCP spec * Used to provide metadata about audience, priority, and modification times */ export interface Annotations { audience?: ("user" | "assistant")[]; priority?: number; // 0-1 scale lastModified?: string; // ISO 8601 timestamp } /** * Text content type */ export interface TextContent { type: "text"; text: string; annotations?: Annotations; } /** * Image content type */ export interface ImageContent { type: "image"; data: string; // base64-encoded mimeType: string; annotations?: Annotations; } /** * Audio content type */ export interface AudioContent { type: "audio"; data: string; // base64-encoded mimeType: string; annotations?: Annotations; } /** * Resource link type - references a resource that can be fetched */ export interface ResourceLinkContent { type: "resource_link"; uri: string; name?: string; description?: string; mimeType?: string; annotations?: Annotations; } /** * Embedded resource type - includes the full resource content */ export interface EmbeddedResourceContent { type: "resource"; resource: { uri: string; mimeType?: string; text?: string; blob?: string; // base64-encoded annotations?: Annotations; }; } /** * Union type of all supported content types */ export type ToolContent = | TextContent | ImageContent | AudioContent | ResourceLinkContent | EmbeddedResourceContent; /** * Tool result structure per MCP spec */ export interface ToolResult { content: ToolContent[]; isError?: boolean; // Flag for tool execution errors _meta?: Record<string, unknown>; // Internal metadata (not part of MCP spec) structuredContent?: Record<string, unknown>; // For structured responses } export abstract class BaseTool { protected context: ToolContext; constructor(context: ToolContext) { this.context = context; } abstract register(): void; // Tools can override to describe capabilities for introspection getCapabilities(): ToolCapabilityDescriptor { return { tool: "unknown", summary: "No capability metadata provided.", }; } /** * Create a basic tool result with content */ protected result(content: ToolContent[], isError = false): ToolResult { return { content, isError }; } /** * Create text content with optional annotations */ protected textContent(text: string, annotations?: Annotations): TextContent { return annotations ? { type: "text", text, annotations } : { type: "text", text }; } /** * Create image content with base64-encoded data */ protected imageContent( data: string, mimeType: string, annotations?: Annotations, ): ImageContent { return annotations ? { type: "image", data, mimeType, annotations } : { type: "image", data, mimeType }; } /** * Create audio content with base64-encoded data */ protected audioContent( data: string, mimeType: string, annotations?: Annotations, ): AudioContent { return annotations ? { type: "audio", data, mimeType, annotations } : { type: "audio", data, mimeType }; } /** * Create a resource link */ protected resourceLink( uri: string, options?: { name?: string; description?: string; mimeType?: string; annotations?: Annotations; }, ): ResourceLinkContent { return { type: "resource_link", uri, ...options, }; } /** * Create an embedded resource */ protected embeddedResource( uri: string, content: { text?: string; blob?: string }, options?: { mimeType?: string; annotations?: Annotations; }, ): EmbeddedResourceContent { return { type: "resource", resource: { uri, ...content, ...options, }, }; } /** * Create a simple text result from one or more messages */ // biome-ignore lint/suspicious/noExplicitAny: helper returns MCP-compatible payload without rigid typing protected textResult(...messages: string[]): ToolResult { return this.result(messages.map((message) => this.textContent(message))); } /** * Create an error result with tool execution error flag * Per MCP spec, tool execution errors should have isError: true */ protected errorResult( errorMessage: string, additionalContext?: string[], ): ToolResult { const content: ToolContent[] = [this.textContent(errorMessage)]; if (additionalContext) { content.push(...additionalContext.map((ctx) => this.textContent(ctx))); } return { content, isError: true }; } /** * Create a structured result with both human-readable and machine-readable content * Per MCP spec, should include both text content and structuredContent for backwards compatibility */ protected structuredResult( payload: Record<string, unknown>, summary?: string | string[], annotations?: Annotations, ): ToolResult { // Always include text serialization for backwards compatibility const textSummary = summary ? Array.isArray(summary) ? summary : [summary] : [JSON.stringify(payload, null, 2)]; const content = textSummary.map((text) => this.textContent(text, annotations), ); return { content, structuredContent: { ...payload, success: payload.success ?? true, }, }; } protected buildUrl(endpoint: string, params: URLSearchParams): string { return this.context.buildUrl(endpoint, params); } protected async parseResponse( response: Response, toolName: string, requestedRetmode?: string, ): Promise<string | any> { return this.context.parseResponse(response, toolName, requestedRetmode); } protected formatResponseData(data: any): string { return this.context.formatResponseData(data); } protected isValidDatabase(db: string): boolean { return this.context.isValidDatabase(db); } protected getApiKey(): string | undefined { return this.context.getApiKey(); } protected getEnvironment(): Env | undefined { return this.context.getEnvironment(); } /** * Helper to create a proper empty input schema per MCP spec * For tools with no parameters, use { type: "object", additionalProperties: false } */ protected emptySchema() { return z.object({}); } /** * Register a tool with the MCP server * Supports title, outputSchema, and aliases per MCP 2025-11-25 spec */ protected registerTool( name: string, description: string, // biome-ignore lint/suspicious/noExplicitAny: MCP server accepts heterogeneous schemas schema: any, // biome-ignore lint/suspicious/noExplicitAny: MCP server accepts heterogeneous handlers handler: any, options?: { aliases?: string[]; title?: string; // biome-ignore lint/suspicious/noExplicitAny: outputSchema accepts any valid JSON Schema outputSchema?: any; annotations?: Record<string, unknown>; }, ): void { // Validate tool name per MCP spec (1-128 chars, specific character set) if (name.length < 1 || name.length > 128) { throw new Error( `Tool name "${name}" must be between 1 and 128 characters`, ); } if (!/^[A-Za-z0-9_.-]+$/.test(name)) { throw new Error( `Tool name "${name}" contains invalid characters. Only A-Z, a-z, 0-9, _, -, and . are allowed`, ); } // Register the main tool using SDK's registerTool() method for full support // biome-ignore lint/suspicious/noExplicitAny: config object accepts various schema types const config: any = { description, inputSchema: schema, }; if (options?.title) { config.title = options.title; } if (options?.outputSchema) { config.outputSchema = options.outputSchema; } if (options?.annotations) { config.annotations = options.annotations; } this.context.server.registerTool(name, config, handler); // Register aliases if provided (using simpler tool() method for aliases) if (options?.aliases) { for (const alias of options.aliases) { this.context.server.tool( alias, `${description} (alias for ${name})`, schema, handler, ); } } } }

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/QuentinCody/entrez-mcp-server'

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