Skip to main content
Glama
by wei
ARCHITECTURE.md12.4 kB
# Architecture Documentation ## Overview The HackerNews MCP Server is a Model Context Protocol (MCP) server that provides structured access to HackerNews content via the Algolia API. It exposes 5 tools for AI assistants to search, retrieve, and explore HackerNews data. ## System Design ### High-Level Architecture ```mermaid flowchart TB client[MCP Client\nClaude Desktop, etc.] client -->|stdio transport\n(JSON-RPC)| server subgraph server [HackerNews MCP Server] protocol[Protocol Layer\n- Server initialization\n- Tool registration\n- Request handling] tools[Tool Layer\n- search-posts\n- get-front-page\n- get-latest-posts\n- get-item\n- get-user] services[Service Layer\n- HNAPIClient wrapper\n- Timeout handling\n- Response transform] utils[Utilities\n- Zod validation\n- Error mapping] protocol --> tools --> services --> utils end server -->|HTTPS| api[HackerNews Algolia API\nhttps://hn.algolia.com/api/v1] ``` ## Component Details ### 1. MCP Protocol Layer (`src/index.ts`) **Responsibilities**: - Initialize MCP Server instance - Register all available tools - Handle ListTools requests - Route CallTool requests to appropriate handlers - Manage stdio transport connection **Key Code**: ```typescript const server = new Server({ name: "hackernews-mcp-server", version: "1.0.0" }, { capabilities: { tools: {} } }); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [/* tool metadata */] }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { // Route to appropriate tool handler }); ``` **Transport**: - Uses `StdioServerTransport` for communication - JSON-RPC messages over stdin/stdout - No network ports required - Spawned as child process by MCP client --- ### 2. Tool Layer (`src/tools/`) Each tool is implemented in a separate file following a consistent pattern: **File Structure**: ``` src/tools/ ├── search-posts.ts # Keyword search with filters ├── get-front-page.ts # Front page retrieval ├── get-latest-posts.ts # Latest posts by date ├── get-item.ts # Item details with comments └── get-user.ts # User profile retrieval ``` **Tool Pattern**: ```typescript // 1. Input schema (Zod) export const ToolInputSchema = z.object({ /* ... */ }); // 2. Output schema (Zod) export const ToolOutputSchema = z.object({ /* ... */ }); // 3. Handler function export async function toolHandler(input: unknown): Promise<CallToolResult> { try { const validatedInput = ToolInputSchema.parse(input); const result = await apiClient.method(validatedInput); const validatedOutput = ToolOutputSchema.parse(result); return createSuccessResult(validatedOutput); } catch (error) { return handleAPIError(error, 'tool-name'); } } // 4. Tool metadata for MCP registration export const toolMetadata = { name: 'tool-name', description: '...', inputSchema: { /* JSON Schema */ } }; ``` **Design Principles**: - **Separation**: One file per tool for maintainability - **Validation**: Both input and output validated with Zod - **Error Handling**: Consistent error responses across all tools - **Documentation**: Inline JSDoc and comprehensive descriptions --- ### 3. Service Layer (`src/services/hn-api.ts`) **HNAPIClient Class**: Wraps the HackerNews Algolia API with: - Timeout handling (default 5 seconds) - Error transformation - Type-safe responses - Reusable HTTP client **Methods**: ```typescript class HNAPIClient { async search(params: SearchParams): Promise<SearchResult> async searchByDate(params: SearchParams): Promise<SearchResult> async getItem(itemId: string): Promise<ItemResult> async getUser(username: string): Promise<HNUser> private async fetchWithTimeout(url: string): Promise<any> private buildQueryString(params: SearchParams): string private transformItemResult(item: any): ItemResult } ``` **Error Handling**: - Network errors (timeout, connection failure) - HTTP errors (404, 429, 500) - JSON parsing errors - AbortController for timeout management **Singleton Pattern**: ```typescript export const hnApi = new HNAPIClient(); ``` --- ### 4. Type System (`src/types/`) **Structure**: ``` src/types/ ├── hn-types.ts # HackerNews API types ├── mcp-types.ts # MCP protocol types └── index.ts # Re-exports + tool input/output types ``` **Key Types**: - `HNItem` - Base type for all HN items - `HNStory`, `HNComment` - Specialized item types - `HNUser` - User profile - `SearchResult` - Search/browse response - `ItemResult` - Single item with nested children - `ToolResult` - MCP tool response format **Type Safety**: - Strict TypeScript mode enabled - Zod schemas provide runtime validation - Type inference from schemas - No `any` types except for recursive structures --- ### 5. Utilities (`src/utils/`) #### Validators (`validators.ts`) **Zod Schemas for Each Tool**: ```typescript SearchPostsInputSchema GetFrontPageInputSchema GetLatestPostsInputSchema GetItemInputSchema GetUserInputSchema ``` **Validation Functions**: ```typescript function validateInput<T>(schema: ZodSchema<T>, input: unknown): T function safeValidateInput<T>(schema, input): Result<T> ``` **Validation Rules**: - Query: min 1 character - Page: non-negative integer - HitsPerPage: 1-1000 range - Username: alphanumeric + underscores - ItemId: non-empty string #### Error Handlers (`error-handlers.ts`) **Error Mapping Functions**: ```typescript handleValidationError(error: ZodError): CallToolResult handleAPIError(error: unknown, context: string): CallToolResult checkItemExists(data, type, id): CallToolResult | null ``` **Success Helpers**: ```typescript createSuccessResult(data: unknown): CallToolResult createErrorResult(error: ErrorResponse): CallToolResult ``` **Error Types**: - `validation` - Input validation failures - `api` - HackerNews API errors - `network` - Timeout or connectivity issues - `not_found` - Item/user doesn't exist - `rate_limit` - Rate limit exceeded --- ## Data Flow ### Search Request Example ```mermaid flowchart TD A[MCP client sends CallToolRequest] --> B[Server reads JSON-RPC on stdin] B --> C[CallToolRequestSchema handler executes] C --> D[Route to searchPostsTool(args)] D --> E[Validate with SearchPostsInputSchema] E --> F[Invoke HNAPIClient.search()] F --> G[HTTP GET https://hn.algolia.com/api/v1/search] G --> H[Parse and validate response] H --> I[Wrap with createSuccessResult()] I --> J[Send CallToolResult on stdout] ``` ### Error Flow ```mermaid flowchart TD E1[Error occurs] --> E2[tool handler catch block] E2 --> E3[Determine error type] E3 --> E4[Call handleAPIError() or handleValidationError()] E4 --> E5[Create structured ErrorResponse] E5 --> E6[Wrap with createErrorResult()] E6 --> E7[Mark isError: true] E7 --> E8[Return response to client] ``` --- ## Testing Strategy ### Test Organization ``` tests/ ├── unit/ # Pure unit tests (no I/O) │ ├── validators.test.ts │ └── error-handlers.test.ts ├── integration/ # Tests with real API calls │ ├── hn-api.test.ts │ └── tools/ │ ├── get-front-page.test.ts │ └── (other tools) └── contract/ # MCP protocol compliance └── get-front-page.test.ts ``` ### Test Types **Unit Tests**: - Validators: All validation rules - Error handlers: All error mapping scenarios - Pure functions with no side effects **Integration Tests**: - HN API client: Real API calls - Tool handlers: End-to-end with live data - Network error scenarios **Contract Tests**: - Input schema validation - Output schema validation - MCP protocol compliance ### Coverage Requirements - **Overall**: 80%+ (Constitution requirement) - **Validators**: 100% - **Error handlers**: 100% - **API client**: 90%+ - **Tools**: 85%+ per tool --- ## Configuration ### Server Config ```typescript const SERVER_CONFIG = { name: "hackernews-mcp-server", version: "1.0.0" }; ``` ### API Configuration ```typescript const HN_API_BASE_URL = "https://hn.algolia.com/api/v1"; const DEFAULT_TIMEOUT = 5000; // 5 seconds ``` ### Timeouts - **Request Timeout**: 5 seconds - **AbortController**: Cancels slow requests - **Retry**: None (fail fast) --- ## Security Considerations ### Input Validation - All inputs validated with Zod schemas - Regex validation for usernames - Range validation for pagination - No SQL injection risk (read-only API) ### Output Sanitization - API responses validated before returning - Structured content for programmatic access - JSON stringification of all outputs ### Rate Limiting - Respect HackerNews API limits (10k/hour) - Clear error messages on 429 - No server-side rate limiting (fail fast) ### Error Messages - Never expose stack traces to clients - Generic error messages for internal failures - Detailed errors only for validation/user errors --- ## Performance Optimizations ### Request Handling - **Native fetch**: No dependency overhead - **Timeout**: Prevents hanging requests - **Streaming**: Not applicable (small responses) ### Memory Management - No caching (always fresh data) - Short-lived request handlers - Minimal state (only server instance) ### Response Times - Average: 200-800ms - 95th percentile: <2 seconds - Large item requests: <3 seconds --- ## Deployment ### Build Process ```bash npm run build # TypeScript compilation ``` **Output**: `dist/` directory with compiled JavaScript ### Running ```bash # Via npm npm start # Via executable ./dist/index.js # Via MCP client # Configured in client's MCP settings ``` ### Dependencies **Production**: - `@modelcontextprotocol/sdk` - MCP protocol - `zod` - Schema validation **Development**: - `typescript` - Compilation - `@biomejs/biome` - Linting/formatting - `vitest` - Testing --- ## Monitoring & Debugging ### Logging - All logs go to stderr (stdout reserved for MCP) - Startup message: "HackerNews MCP Server running on stdio" - Errors logged before sending to client ### Debugging **Enable Debug Mode**: ```typescript const SERVER_CONFIG = { name: "hackernews-mcp-server", version: "1.0.0", debug: true // Add this }; ``` **Debug Information**: - Request routing - API calls - Validation errors - Response times ### Error Investigation 1. Check stderr logs 2. Verify input parameters 3. Test API endpoint directly 4. Check network connectivity 5. Review HackerNews API status --- ## Scaling Considerations ### Current Design - Single-process server - Stateless (no shared state) - No connection pooling needed - No database ### Future Enhancements If scaling needed: - **Caching**: Add Redis for popular items - **Load Balancing**: Run multiple instances - **Rate Limiting**: Server-side throttling - **Monitoring**: Request metrics, error rates ### Limitations - Rate limited by HackerNews API (10k/hour) - Single IP address per instance - No persistent connections - No request batching --- ## Code Style & Standards ### TypeScript - Strict mode enabled - No `any` types (justified exceptions only) - Explicit return types on public functions - Consistent error handling ### Formatting - Biome for linting and formatting - Tabs for indentation - Line width: 100 characters - Double quotes for strings ### Documentation - JSDoc on all public functions - Type documentation inline - Comprehensive README - API reference documentation --- ## Maintenance ### Updating Dependencies ```bash npm outdated npm update npm audit fix ``` ### Testing After Updates ```bash npm run lint npm test npm run build ``` ### Version Bumping Follow semantic versioning: - **MAJOR**: Breaking changes - **MINOR**: New features (backward compatible) - **PATCH**: Bug fixes --- ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for: - Development setup - Code style guidelines - Testing requirements - Pull request process - Issue templates --- ## Additional Resources - [MCP Protocol Documentation](https://modelcontextprotocol.io) - [HackerNews API Documentation](https://hn.algolia.com/api) - [TypeScript Handbook](https://www.typescriptlang.org/docs/) - [Vitest Documentation](https://vitest.dev/) - [Zod Documentation](https://zod.dev/)

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/wei/hn-mcp-server'

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