Skip to main content
Glama

ClinicalTrials.gov MCP Server

best-practices.md7.25 kB
# Project Best Practices and Architectural Guide This document outlines the core architectural patterns and best practices established for this project. Adhering to these guidelines will ensure consistency, maintainability, and robustness as the codebase evolves. ## 1. Centralized Error Handling The cornerstone of our architecture is a centralized, consistent approach to error handling, managed by the `ErrorHandler` utility. ### Core Principle: Logic Throws, Handlers Catch - **Core Logic Files** (e.g., `logic.ts`): These files must focus exclusively on business logic. When an error occurs (e.g., failed validation, API error), they must `throw` a structured `McpError`. They **must not** contain `try...catch` blocks for formatting the final tool response. - **Handler/Registration Files** (e.g., `registration.ts`): These files are responsible for invoking the core logic. The tool handler function must wrap the call to the logic in a `try...catch` block. This block is the designated place to handle any thrown errors, format the final `CallToolResult` (both for success and error cases), and interact with the `ErrorHandler`. ### The `McpError` Class Always `throw` instances of `McpError` for predictable, structured errors. This ensures that errors are machine-readable and carry sufficient context for debugging. ```typescript // From: src/types-global/errors.ts export class McpError extends Error { public readonly code: BaseErrorCode; public readonly details?: Record<string, unknown>; // ... constructor } // Example usage in a logic file: throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid input provided.', { ...context, invalidField: 'some_field', }); ``` - **`code`**: Use a value from the `BaseErrorCode` enum (e.g., `NOT_FOUND`, `VALIDATION_ERROR`). - **`message`**: Provide a clear, human-readable description of the error. - **`details`**: Include a structured object with relevant context (e.g., the invalid input, the request ID). ### The `ErrorHandler` Utility The `ErrorHandler` is used within the `catch` block of a handler to ensure all errors are processed and logged consistently before being sent to the client. ```typescript // From: src/utils/internal/errorHandler.ts export class ErrorHandler { public static handleError( error: unknown, options: ErrorHandlerOptions, ): Error { // ... implementation } } ``` ### Example: The Recommended Pattern The `fetchPubMedContent` tool registration is the canonical example of this pattern. ```typescript // In: src/mcp-server/tools/fetchPubMedContent/registration.ts server.tool( toolName, toolDescription, FetchPubMedContentInputSchema._def.schema.shape, async (input, mcpContext): Promise<CallToolResult> => { const richContext = requestContextService.createRequestContext({ /* ... */ }); try { // 1. Call the core logic. It will return data on success or throw on failure. const result = await fetchPubMedContentLogic(input, richContext); // 2. Format the success response inside the handler. return { content: [{ type: 'text', text: result.content }], isError: false, }; } catch (error) { // 3. Catch any error from the logic and process it. const handledError = ErrorHandler.handleError(error, { operation: 'fetchPubMedContentToolHandler', context: richContext, input, rethrow: false, // Prevent re-throwing as we are formatting the final response. }); // 4. Format the final error response. const mcpError = handledError instanceof McpError ? handledError : new McpError(/* ... */); return { content: [ { type: 'text', text: JSON.stringify({ error: { code: mcpError.code, message: mcpError.message, details: mcpError.details, }, }), }, ], isError: true, }; } }, ); ``` ## 2. Structured and Contextual Logging All logging must be structured and include a `RequestContext` to enable effective tracing and debugging across distributed operations. ### Core Principle: Every Log Entry Must Be Traceable The `Logger` singleton is the sole entry point for logging. It requires a message and a `RequestContext` object. ### The `RequestContext` Generated by `requestContextService.createRequestContext()`, every context object provides: - `requestId`: A unique ID for the entire operation lifecycle. - `timestamp`: An ISO 8601 timestamp. - `operation`: A descriptive name for the current function or process (e.g., `fetchPubMedContentLogic`). - `parentRequestId`: (Optional) The ID of the calling context, creating a parent-child trace. ### Example: Logging in a Function ```typescript // In: src/mcp-server/tools/fetchPubMedContent/logic.ts import { logger, requestContextService, RequestContext } from "../../../utils/index.js"; export async function fetchPubMedContentLogic(input: ..., parentRequestContext: RequestContext): Promise<...> { const toolLogicContext = requestContextService.createRequestContext({ parentRequestId: parentRequestContext.requestId, operation: "fetchPubMedContentLogic", input: sanitizeInputForLogging(input), }); logger.info("Executing fetch_pubmed_content tool", toolLogicContext); // ... logic ... logger.notice("Successfully executed fetch_pubmed_content tool.", { ...toolLogicContext, articlesReturned: articlesCount, }); } ``` ## 3. Tool and Module Structure A consistent file and module structure is critical for scalability and ease of navigation. ### Directory Structure Each tool must reside in its own directory (e.g., `src/mcp-server/tools/myNewTool/`) and follow this pattern: - **`registration.ts`**: The public-facing entry point. It registers the tool, defines its description, and contains the error-handling `try...catch` block that calls the core logic. - **`logic.ts`**: The primary implementation file. It defines the Zod schema, exports the input/output types, and contains the core business logic for the tool. - **`logic/` (optional subdirectory)**: For highly complex tools, this directory can be used to further break down the logic into smaller, more manageable files (e.g., `sub-handler.ts`, `formatter.ts`). The main `logic.ts` would then import from and orchestrate these components. ### Schema and Type Definition - The Zod schema and its inferred TypeScript types for a tool's input and output **must** be defined and exported from the **`logic.ts`** file. - The `registration.ts` file **must** import the schema and types from `logic.ts`. This one-way dependency prevents circular references. ### Example: File Structure ``` src/mcp-server/tools/fetchPubMedContent/ ├── index.ts // Barrel file, exports registration function ├── registration.ts // Imports from logic.ts. Handles registration and error catching. └── logic.ts // Exports the main logic function and the Zod schema. ``` By adhering to these guidelines, we ensure the project remains robust, maintainable, and easy for any developer to contribute to effectively.

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/cyanheads/clinicaltrialsgov-mcp-server'

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