ARCHITECTURE.md•12.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/)