# Research: HackerNews MCP Server
**Date**: October 30, 2025
**Purpose**: Resolve all technical unknowns and establish best practices for implementation
## Research Tasks
### 1. HackerNews Algolia API Capabilities
**Task**: Document all available endpoints, parameters, and response formats
**Findings**:
- **Base URL**: `https://hn.algolia.com/api/v1/`
- **Key Endpoints**:
- `search` - Full-text search with rich filtering
- `search_by_date` - Chronological search
- `items/:id` - Get specific item (story, comment, poll, etc.)
- `users/:username` - Get user profile
- **Search Parameters**:
- `query` - Search terms
- `tags` - Filter by type (story, comment, poll, pollopt, show_hn, ask_hn, front_page, author_username, story_id)
- `numericFilters` - Filter by numeric fields (created_at_i, points, num_comments)
- `page` - Pagination (0-indexed)
- `hitsPerPage` - Results per page (default 20, max 1000)
- **Response Format**: JSON with hits array, metadata (nbHits, nbPages, page, hitsPerPage, processingTimeMS)
- **Rate Limiting**: 10,000 requests/hour/IP (documented in API)
**Decision**: Use search endpoint for keyword/filter queries, items/:id for specific posts, users/:username for profiles
**Rationale**: API provides all required functionality natively. No workarounds needed.
**Alternatives Considered**:
- Firebase real-time API: More complex, requires authentication, less suitable for MCP server use case
- Scraping HN website: Unreliable, violates ToS, unnecessary given API availability
---
### 2. Model Context Protocol (MCP) Best Practices
**Task**: Study MCP SDK patterns and establish implementation guidelines
**Findings**:
- **Server Structure Pattern**:
- Main entry point creates Server instance with stdio transport
- Tool handlers registered via `setRequestHandler(CallToolRequestSchema)`
- List tools via `setRequestHandler(ListToolsRequestSchema)`
- **Tool Definition Pattern**:
- Use Zod schemas for input validation
- Convert to JSON Schema via `zod-to-json-schema`
- Tools have name, description, inputSchema
- **Error Handling Pattern**:
- Return `{ content: [...], isError: true }` for errors
- Include error message in content as text
- Validate inputs before processing
- **Separation Pattern**:
- Business logic in separate files/classes (e.g., `lib.ts`)
- Tool handlers delegate to business logic
- Keep index.ts focused on MCP protocol handling
- **Testing Pattern**:
- Test business logic independently
- Mock external APIs
- Use vitest with globals, node environment
- Coverage provider: v8
**Decision**: Follow filesystem and sequential-thinking server patterns:
1. Separate tool implementations into `src/tools/` directory (one per tool)
2. Business logic in `src/services/` (API client, rate limiter)
3. Type definitions in `src/types/`
4. Main entry in `src/index.ts` handles MCP protocol only
**Rationale**: Proven pattern from official MCP servers, enables testability, clear separation of concerns
**Alternatives Considered**:
- Monolithic index.ts: Poor testability, violates SRP, harder to maintain
- Class-based architecture: Over-engineered for stateless API proxy, functional approach simpler
---
### 3. TypeScript Strict Mode Configuration
**Task**: Define tsconfig.json settings for maximum type safety
**Findings**:
- **Required Strict Flags**:
```json
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}
```
- **Strict Mode Components**:
- `noImplicitAny`: true (catches missing types)
- `strictNullChecks`: true (prevents null/undefined bugs)
- `strictFunctionTypes`: true (ensures function param safety)
- `strictBindCallApply`: true (prevents binding errors)
- `strictPropertyInitialization`: true (class properties must initialize)
- `noImplicitThis`: true (this must be typed)
- `alwaysStrict`: true (emits "use strict")
**Decision**: Use strict: true with ES2022 target, Node16 module resolution
**Rationale**: Constitution mandates strict mode, ES2022 provides modern features, Node16 supports ESM properly
**Alternatives Considered**:
- Loose mode with gradual tightening: Violates constitution, accumulates technical debt
- CommonJS modules: ESM is modern standard, required for MCP SDK
---
### 4. Biome Configuration for TypeScript/Node.js
**Task**: Establish Biome linting and formatting rules
**Findings**:
- **Biome Configuration**:
```json
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"ignore": ["node_modules", "dist", "coverage"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"noNonNullAssertion": "error",
"useConst": "error"
},
"suspicious": {
"noExplicitAny": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"trailingCommas": "es5"
}
}
}
```
**Decision**: Use Biome with recommended rules plus strict TypeScript rules (no any, no non-null assertions)
**Rationale**: Constitution mandates Biome, recommended rules cover best practices, custom rules enforce type safety
**Alternatives Considered**:
- ESLint + Prettier: Violates constitution requirement for Biome only
- Minimal rules: Doesn't enforce quality standards required by constitution
---
### 5. Vitest Testing Strategy
**Task**: Define test structure and coverage requirements
**Findings**:
- **Test Categories**:
- **Contract Tests**: Validate external API contracts (HN API response structure)
- **Integration Tests**: Test tool workflows end-to-end (mock HTTP, verify tool output)
- **Unit Tests**: Test individual functions (API client methods, validators, utils)
- **Vitest Configuration**:
```typescript
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.test.ts'],
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'dist/**'],
reporter: ['text', 'json', 'html'],
lines: 90,
functions: 90,
branches: 90,
statements: 90
}
}
});
```
- **Mocking Strategy**:
- Use `vi.mock()` for external API calls
- Use `vi.spyOn()` for internal function monitoring
- Create fixtures for common API responses
**Decision**: Three-layer test structure with 90% coverage threshold, vitest with v8 provider
**Rationale**: Aligns with constitution TDD requirement, matches MCP server examples, v8 is fast and accurate
**Alternatives Considered**:
- Jest: Slower, Vitest is modern replacement with better TypeScript support
- Lower coverage threshold: Constitution requires ≥90%
---
### 6. Error Handling Patterns
**Task**: Define error handling strategy for API failures, validation errors, rate limits
**Findings**:
- **Error Categories**:
1. **Validation Errors**: Invalid tool inputs (wrong types, missing fields)
2. **API Errors**: HN API failures (network, 404, 500, rate limit)
3. **Business Logic Errors**: Invalid IDs, non-existent users, empty results
- **MCP Error Response Format**:
```typescript
{
content: [{
type: "text",
text: JSON.stringify({
error: "Error message",
type: "validation_error" | "api_error" | "not_found" | "rate_limit",
details: { /* contextual info */ }
})
}],
isError: true
}
```
- **Error Handler Pattern**:
```typescript
try {
// Validate input
const validated = schema.parse(args);
// Call API
const result = await apiClient.fetch(validated);
// Return success
return { content: [...] };
} catch (error) {
if (error instanceof ZodError) {
return formatValidationError(error);
}
if (error instanceof ApiError) {
return formatApiError(error);
}
return formatUnknownError(error);
}
```
**Decision**: Use centralized error formatter utilities, include error type and details in responses
**Rationale**: Consistent error format aids debugging, type categorization enables client-side handling, details provide context
**Alternatives Considered**:
- Throw errors: MCP pattern uses return values, not exceptions
- Generic error messages: Insufficient information for troubleshooting
---
### 7. Rate Limiting Implementation
**Task**: Design rate limiter respecting 10,000 req/hour HN API limit
**Findings**:
- **Rate Limit Details**: 10,000 requests/hour/IP address
- **Strategies**:
1. **Token Bucket**: Allow bursts, refill over time
2. **Sliding Window**: Precise per-hour tracking
3. **Client-side only**: Track requests, warn when approaching limit
- **Implementation Options**:
- In-memory counter (resets on server restart)
- No persistent storage (stateless server)
- Log warnings at 80%, 90%, 95% thresholds
- Return rate_limit error at 100%
- **Token Bucket Algorithm**:
```typescript
class RateLimiter {
private tokens: number = 10000;
private lastRefill: number = Date.now();
private readonly maxTokens = 10000;
private readonly refillRate = maxTokens / (60 * 60 * 1000); // per ms
async checkLimit(): Promise<void> {
this.refill();
if (this.tokens < 1) {
throw new RateLimitError("Rate limit exceeded");
}
this.tokens -= 1;
}
private refill(): void {
const now = Date.now();
const elapsed = now - this.lastRefill;
const tokensToAdd = elapsed * this.refillRate;
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
```
**Decision**: Implement token bucket algorithm with in-memory tracking, warning thresholds at 80/90/95%
**Rationale**: Token bucket allows bursts (better UX), in-memory is simple and sufficient, warnings prevent hitting limit
**Alternatives Considered**:
- No rate limiting: Risk hitting API limit and service disruption
- Persistent storage: Over-engineered for stateless server, adds complexity
- Fixed window: Less smooth, doesn't handle bursts well
---
### 8. Package.json Configuration
**Task**: Define NPM package metadata, scripts, and dependencies
**Findings**:
- **Required Dependencies**:
- `@modelcontextprotocol/sdk`: Latest (^1.0.0 or newer)
- `zod`: ^3.23.0 (schema validation)
- `zod-to-json-schema`: ^3.23.0 (convert zod to JSON schema)
- **Dev Dependencies**:
- `typescript`: ^5.0.0
- `@biomejs/biome`: ^1.9.0
- `vitest`: ^2.0.0
- `@vitest/coverage-v8`: ^2.0.0
- `@types/node`: ^22.0.0
- **Scripts**:
- `build`: `tsc` (compile TypeScript)
- `dev`: `tsc --watch` (watch mode)
- `test`: `vitest run` (run tests once)
- `test:watch`: `vitest` (watch mode)
- `test:coverage`: `vitest run --coverage` (with coverage)
- `lint`: `biome check src tests` (lint check)
- `lint:fix`: `biome check --write src tests` (auto-fix)
- `format`: `biome format --write src tests` (format code)
- `typecheck`: `tsc --noEmit` (type check only)
- `prepare`: `npm run build` (pre-publish hook)
- **Package Metadata**:
- `name`: `@username/hn-mcp-server` or `hn-mcp-server`
- `version`: `0.1.0` (initial)
- `type`: `module` (ESM)
- `main`: `dist/index.js`
- `bin`: `{ "hn-mcp-server": "./dist/index.js" }` (CLI executable)
- `files`: `["dist", "README.md", "LICENSE"]`
- `engines`: `{ "node": ">=22.0.0" }`
**Decision**: Use ESM modules, provide both library and CLI interfaces, follow semantic versioning
**Rationale**: ESM is modern standard, CLI enables direct execution, semantic versioning standard for NPM
**Alternatives Considered**:
- CommonJS: Outdated, ESM is required for MCP SDK
- No CLI: Less convenient for users, CLI is standard for MCP servers
---
## Technology Stack Summary
| Category | Technology | Version | Rationale |
|----------|-----------|---------|-----------|
| Runtime | Node.js | ≥22.0.0 | Latest LTS, excellent ESM support |
| Language | TypeScript | ^5.0.0 | Type safety, constitution requirement |
| MCP SDK | @modelcontextprotocol/sdk | Latest | Official SDK, stdio transport |
| Validation | zod | ^3.23.0 | Type-safe schema validation |
| Testing | vitest | ^2.0.0 | Fast, modern, excellent TS support |
| Linting/Formatting | Biome | ^1.9.0 | Constitution requirement, unified tooling |
| HTTP Client | Native fetch | Built-in | Node 18+ has native fetch, no deps needed |
---
## Implementation Checklist
- [ ] Initialize package.json with dependencies and scripts
- [ ] Configure tsconfig.json with strict mode
- [ ] Configure biome.json with recommended + strict rules
- [ ] Configure vitest.config.ts with 90% coverage threshold
- [ ] Create src/ directory structure (index, types, tools, services, lib)
- [ ] Create tests/ directory structure (contract, integration, unit)
- [ ] Implement HN API client with fetch
- [ ] Implement rate limiter with token bucket algorithm
- [ ] Implement validation helpers with zod
- [ ] Implement error handler utilities
- [ ] Implement MCP tools (search, front-page, get-post, get-user)
- [ ] Write comprehensive tests for all layers
- [ ] Create README.md with installation, usage, examples
- [ ] Create LICENSE file (MIT)
- [ ] Verify 90%+ test coverage
- [ ] Verify all Biome checks pass
- [ ] Verify TypeScript strict mode with no errors
---
## Next Steps
Proceed to **Phase 1: Design & Contracts**
- Create data-model.md (entity definitions, validation rules)
- Create contracts/tools.json (MCP tool definitions)
- Create quickstart.md (usage guide)
- Update agent context (.github/copilot-instructions.md)