# Developer Guide

# European Parliament MCP Server - Developer Guide

Complete guide for contributing and extending the MCP server
Project structure, development workflow, and best practices

## 📋 Table of Contents

- Getting Started
- Project Structure
- Development Workflow
- Adding New Tools
- Testing Strategy
- Code Style & Conventions
- Security Considerations
- Performance Guidelines
- Contributing Workflow
- Release Process

## 🚀 Getting Started

### Prerequisites

Required:

- Node.js 26.x or higher
- npm 10.x or higher
- Git
- TypeScript knowledge
- MCP protocol familiarity

Recommended:

- VS Code with extensions:
  - ESLint
  - Prettier
  - TypeScript and JavaScript Language Features
- Docker (for containerized development)

### Initial Setup

    # Clone repository
    git clone https://github.com/Hack23/European-Parliament-MCP-Server.git
    cd European-Parliament-MCP-Server

    # Install dependencies
    npm install

    # Build project
    npm run build

    # Run tests
    npm test

    # Start development server
    npm run dev

### Development Environment

    # Install recommended tools
    npm install -g tsx vitest

    # Setup git hooks (optional)
    npm run prepare

    # Verify setup
    npm run type-check
    npm run lint
    npm test

## 📁 Project Structure

    European-Parliament-MCP-Server/
    ├── .github/                    # GitHub configuration
    │   ├── workflows/             # CI/CD workflows
    │   │   ├── test-and-report.yml    # Unit tests + coverage
    │   │   ├── integration-tests.yml  # E2E + performance tests
    │   │   └── release.yml            # Automated releases
    │   ├── agents/                # Custom Copilot agents
    │   └── skills/                # Reusable skill patterns
    │
    ├── docs/                      # Documentation
    │   ├── API_USAGE_GUIDE.md    # Tool usage documentation
    │   ├── ARCHITECTURE_DIAGRAMS.md   # Visual architecture
    │   ├── TROUBLESHOOTING.md    # Common issues
    │   ├── DEVELOPER_GUIDE.md    # This file
    │   ├── DEPLOYMENT_GUIDE.md   # Deployment instructions
    │   └── PERFORMANCE_GUIDE.md  # Optimization strategies
    │
    ├── src/                       # Source code
    │   ├── index.ts              # Server entry point
    │   │
    │   ├── tools/                # MCP tool implementations
    │   │   ├── getMEPs.ts
    │   │   ├── getMEPDetails.ts
    │   │   ├── getPlenarySessions.ts
    │   │   ├── getVotingRecords.ts
    │   │   ├── searchDocuments.ts
    │   │   ├── getCommitteeInfo.ts
    │   │   ├── getParliamentaryQuestions.ts
    │   │   ├── analyzeVotingPatterns.ts
    │   │   ├── trackLegislation.ts
    │   │   └── generateReport.ts
    │   │
    │   ├── clients/              # External API clients
    │   │   └── europeanParliamentClient.ts
    │   │
    │   ├── schemas/              # Zod validation schemas
    │   │   └── europeanParliament.ts
    │   │
    │   ├── types/                # TypeScript type definitions
    │   │   └── europeanParliament.ts
    │   │
    │   ├── utils/                # Utility functions
    │   │   ├── cache.ts
    │   │   ├── rateLimiter.ts
    │   │   └── logger.ts
    │   │
    │   └── services/             # Business services
    │       └── MetricsService.ts
    │
    ├── tests/                     # Test files
    │   ├── integration/          # Integration tests
    │   ├── e2e/                  # End-to-end tests
    │   ├── performance/          # Performance benchmarks
    │   └── helpers/              # Test utilities
    │
    ├── dist/                      # Compiled JavaScript (gitignored)
    ├── node_modules/              # Dependencies (gitignored)
    │
    ├── package.json               # Project metadata
    ├── tsconfig.json             # TypeScript configuration
    ├── vitest.config.ts          # Test configuration
    ├── eslint.config.js          # ESLint configuration
    └── README.md                 # Project overview

### Key Directories

Directory | Purpose | File Types
--- | --- | ---
`src/tools/` | MCP tool implementations | `.ts`, `.test.ts`
`src/clients/` | External API clients | `.ts`, `.test.ts`
`src/schemas/` | Zod validation schemas | `.ts`
`src/types/` | TypeScript type definitions | `.ts`
`src/utils/` | Utility functions | `.ts`, `.test.ts`
`tests/integration/` | Integration tests | `.test.ts`
`tests/e2e/` | End-to-end MCP tests | `.e2e.test.ts`
`docs/` | Documentation | `.md`

## 🔄 Development Workflow

### Daily Development

    # 1. Pull latest changes
    git checkout main
    git pull origin main

    # 2. Create feature branch
    git checkout -b feature/my-feature

    # 3. Make changes
    # Edit files...

    # 4. Run tests continuously
    npm run test:watch

    # 5. Lint and format
    npm run lint:fix
    npm run format

    # 6. Build
    npm run build

    # 7. Run full test suite
    npm test

    # 8. Commit changes
    git add .
    git commit -m "feat: add new feature"

    # 9. Push to GitHub
    git push origin feature/my-feature

    # 10. Create Pull Request

### Development Scripts

    # Build
    npm run build              # Production build
    npm run build:watch        # Watch mode

    # Testing
    npm test                   # All tests
    npm run test:unit          # Unit tests only
    npm run test:integration   # Integration tests
    npm run test:e2e           # End-to-end tests
    npm run test:performance   # Performance benchmarks
    npm run test:coverage      # Coverage report
    npm run test:watch         # Watch mode

    # Code Quality
    npm run lint               # Check linting
    npm run lint:fix           # Fix linting issues
    npm run format             # Format with Prettier
    npm run type-check         # TypeScript type checking

    # Other
    npm run dev                # Development server
    npm run knip               # Find unused exports
    npm run test:licenses      # Check license compliance

## 🛠️ Adding New Tools

### Step 1: Define Tool Schema

Create Zod schema in `src/schemas/europeanParliament.ts`:

    /**
     * Schema for new_tool input validation
     */
    export const NewToolSchema = z.object({
      param1: z.string().min(1).max(100),
      param2: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
      param3: z.number().int().min(1).max(100).default(50)
    });

    export type NewToolParams = z.infer<typeof NewToolSchema>;

### Step 2: Define TypeScript Types

Add types in `src/types/europeanParliament.ts`:

    /**
     * Result type for new_tool
     */
    export interface NewToolResult {
      id: string;
      name: string;
      data: unknown[];
      metadata: {
        timestamp: string;
        source: string;
      };
    }

### Step 3: Implement Tool Handler

Create `src/tools/newTool.ts`:

    /**
     * MCP Tool: new_tool
     *
     * Brief description of what this tool does
     *
     * ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
     */

    import { NewToolSchema } from '../schemas/europeanParliament.js';
    import { epClient } from '../clients/europeanParliamentClient.js';
    import type { NewToolResult } from '../types/europeanParliament.js';

    /**
     * New tool handler
     *
     * @param args - Tool arguments
     * @returns MCP tool result
     *
     * @example
     * ```json
     * {
     *   "param1": "value1",
     *   "param2": "2024-01-01"
     * }
     * ```
     */
    export async function handleNewTool(
      args: unknown
    ): Promise<{ content: { type: string; text: string }[] }> {
      // Step 1: Validate input
      const params = NewToolSchema.parse(args);

      try {
        // Step 2: Fetch data from EP API client
        const result = await epClient.getNewData(params);

        // Step 3: Transform to expected format
        const transformed: NewToolResult = {
          id: result.id,
          name: result.name,
          data: result.items,
          metadata: {
            timestamp: new Date().toISOString(),
            source: 'European Parliament'
          }
        };

        // Step 4: Return MCP-compliant response
        return {
          content: [{
            type: 'text',
            text: JSON.stringify(transformed, null, 2)
          }]
        };
      } catch (error) {
        // Step 5: Handle errors without exposing internal details
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        throw new Error(`Failed to execute new_tool: ${errorMessage}`);
      }
    }

    /**
     * Tool metadata for MCP registration
     */
    export const newToolMetadata = {
      name: 'new_tool',
      description: 'Brief description of what this tool does and what data it returns. Include key use cases.',
      inputSchema: {
        type: 'object' as const,
        properties: {
          param1: {
            type: 'string',
            description: 'Description of param1',
            minLength: 1,
            maxLength: 100
          },
          param2: {
            type: 'string',
            description: 'Optional date parameter (YYYY-MM-DD)',
            pattern: '^\\d{4}-\\d{2}-\\d{2}$'
          },
          param3: {
            type: 'number',
            description: 'Page size (1-100)',
            minimum: 1,
            maximum: 100,
            default: 50
          }
        },
        required: ['param1']
      }
    };

### Step 4: Register Tool

Add to `src/index.ts`:

    // Import tool
    import { handleNewTool, newToolMetadata } from './tools/newTool.js';

    // Register in tool handlers map
    const toolHandlers: Record<string, (args: unknown) => Promise<any>> = {
      get_meps: handleGetMEPs,
      // ... other tools ...
      new_tool: handleNewTool  // Add new tool
    };

    // Register metadata in list_tools handler
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          getMEPsToolMetadata,
          // ... other tools ...
          newToolMetadata  // Add new tool metadata
        ]
      };
    });

### Step 5: Add EP API Client Method

If needed, add method to `src/clients/europeanParliamentClient.ts`:

    /**
     * Get new data from European Parliament API
     */
    async getNewData(params: {
      param1: string;
      param2?: string;
    }): Promise<NewToolResult> {
      const cacheKey = `newdata:${params.param1}:${params.param2 ?? 'all'}`;

      // Check cache
      const cached = this.cache.get(cacheKey);
      if (cached) {
        return cached;
      }

      // Rate limiting
      await this.rateLimiter.waitForToken();

      // Build URL
      const url = new URL('/api/v2/newdata', this.baseUrl);
      url.searchParams.append('param1', params.param1);
      if (params.param2) {
        url.searchParams.append('param2', params.param2);
      }

      // Fetch data
      const response = await fetch(url.toString(), {
        headers: {
          'Accept': 'application/ld+json',
          'User-Agent': 'European-Parliament-MCP-Server/1.0'
        }
      });

      if (!response.ok) {
        throw new Error(`EP API error: ${response.status}`);
      }

      const data = await response.json();

      // Transform from JSON-LD to internal format
      const result = this.transformNewData(data);

      // Cache result
      this.cache.set(cacheKey, result);

      // Log access for audit
      this.logger.info('Fetched new data', { param1: params.param1 });

      return result;
    }

    /**
     * Transform EP API JSON-LD to internal format
     */
    private transformNewData(apiData: any): NewToolResult {
      return {
        id: this.toSafeString(apiData['id']),
        name: this.toSafeString(apiData['label']),
        data: Array.isArray(apiData['items']) ? apiData['items'] : [],
        metadata: {
          timestamp: new Date().toISOString(),
          source: 'European Parliament'
        }
      };
    }

### Step 6: Write Tests

Create `src/tools/newTool.test.ts`:

    import { describe, it, expect, vi, beforeEach } from 'vitest';
    import { handleNewTool, newToolMetadata } from './newTool.js';
    import { epClient } from '../clients/europeanParliamentClient.js';

    // Mock EP client
    vi.mock('../clients/europeanParliamentClient.js', () => ({
      epClient: {
        getNewData: vi.fn()
      }
    }));

    describe('newTool', () => {
      beforeEach(() => {
        vi.clearAllMocks();
      });

      describe('Input Validation', () => {
        it('should accept valid parameters', async () => {
          vi.mocked(epClient.getNewData).mockResolvedValue({
            id: 'test-id',
            name: 'Test Name',
            data: [],
            metadata: {
              timestamp: '2024-01-01T00:00:00Z',
              source: 'European Parliament'
            }
          });

          await expect(
            handleNewTool({
              param1: 'value1',
              param2: '2024-01-01'
            })
          ).resolves.toBeDefined();
        });

        it('should reject invalid param1', async () => {
          await expect(
            handleNewTool({
              param1: '',  // Empty not allowed
              param2: '2024-01-01'
            })
          ).rejects.toThrow();
        });

        it('should reject invalid date format', async () => {
          await expect(
            handleNewTool({
              param1: 'value1',
              param2: '01-01-2024'  // Wrong format
            })
          ).rejects.toThrow();
        });

        it('should use default values', async () => {
          vi.mocked(epClient.getNewData).mockResolvedValue({
            id: 'test-id',
            name: 'Test Name',
            data: [],
            metadata: {
              timestamp: '2024-01-01T00:00:00Z',
              source: 'European Parliament'
            }
          });

          await handleNewTool({ param1: 'value1' });

          expect(epClient.getNewData).toHaveBeenCalledWith(
            expect.objectContaining({
              param1: 'value1'
            })
          );
        });
      });

      describe('Response Format', () => {
        it('should return MCP-compliant response', async () => {
          const mockData = {
            id: 'test-id',
            name: 'Test Name',
            data: [{ item: 1 }],
            metadata: {
              timestamp: '2024-01-01T00:00:00Z',
              source: 'European Parliament'
            }
          };

          vi.mocked(epClient.getNewData).mockResolvedValue(mockData);

          const result = await handleNewTool({ param1: 'value1' });

          expect(result).toHaveProperty('content');
          expect(Array.isArray(result.content)).toBe(true);
          expect(result.content[0]).toHaveProperty('type', 'text');
          expect(result.content[0]).toHaveProperty('text');
        });

        it('should return valid JSON', async () => {
          const mockData = {
            id: 'test-id',
            name: 'Test Name',
            data: [],
            metadata: {
              timestamp: '2024-01-01T00:00:00Z',
              source: 'European Parliament'
            }
          };

          vi.mocked(epClient.getNewData).mockResolvedValue(mockData);

          const result = await handleNewTool({ param1: 'value1' });
          const text = result.content[0].text;

          expect(() => JSON.parse(text)).not.toThrow();
        });
      });

      describe('Error Handling', () => {
        it('should handle EP API errors gracefully', async () => {
          vi.mocked(epClient.getNewData).mockRejectedValue(
            new Error('EP API unavailable')
          );

          await expect(
            handleNewTool({ param1: 'value1' })
          ).rejects.toThrow('Failed to execute new_tool');
        });
      });

      describe('Tool Metadata', () => {
        it('should have correct tool name', () => {
          expect(newToolMetadata.name).toBe('new_tool');
        });

        it('should have description', () => {
          expect(newToolMetadata.description).toBeDefined();
          expect(newToolMetadata.description.length).toBeGreaterThan(10);
        });

        it('should define input schema', () => {
          expect(newToolMetadata.inputSchema).toBeDefined();
          expect(newToolMetadata.inputSchema.properties).toBeDefined();
        });
      });
    });

### Step 7: Update Documentation

1. Add tool to `docs/API_USAGE_GUIDE.md`
2. Update tool count in `README.md`
3. Add to Architecture Diagrams if significant

## 🧪 Testing Strategy

### Test Pyramid

            /\
           /E2E\        End-to-End (10%)
          /------\      - Full MCP client-server tests
         / Integr \     Integration (20%)
        /----------\    - EP API client tests
       /   Unit     \   Unit (70%)
      /--------------\  - Tool handlers, schemas, utils

### Unit Tests

Location: Colocated with source files (`*.test.ts`)

Coverage Target: 80% overall, 95% for security-critical code

Example:

    // src/utils/cache.test.ts
    import { describe, it, expect } from 'vitest';
    import { LRUCache } from 'lru-cache';

    describe('Cache', () => {
      it('should store and retrieve values', () => {
        const cache = new LRUCache({ max: 10 });
        cache.set('key', 'value');
        expect(cache.get('key')).toBe('value');
      });

      it('should respect TTL', async () => {
        const cache = new LRUCache({ max: 10, ttl: 100 });
        cache.set('key', 'value');

        await new Promise(resolve => setTimeout(resolve, 150));

        expect(cache.get('key')).toBeUndefined();
      });

      it('should evict oldest when max reached', () => {
        const cache = new LRUCache({ max: 2 });
        cache.set('key1', 'value1');
        cache.set('key2', 'value2');
        cache.set('key3', 'value3');

        expect(cache.has('key1')).toBe(false);
        expect(cache.has('key2')).toBe(true);
        expect(cache.has('key3')).toBe(true);
      });
    });

### Integration Tests

Location: `tests/integration/`

Purpose: Test EP API client integration

Example:

    // tests/integration/epClient.integration.test.ts
    import { describe, it, expect } from 'vitest';
    import { epClient } from '../../src/clients/europeanParliamentClient.js';

    describe('EP API Client Integration', () => {
      it('should fetch MEPs from real API', async () => {
        const result = await epClient.getMEPs({
          country: 'SE',
          limit: 5
        });

        expect(result.data).toBeDefined();
        expect(Array.isArray(result.data)).toBe(true);
        expect(result.data.length).toBeGreaterThan(0);
        expect(result.total).toBeGreaterThan(0);
      });

      it('should respect rate limiting', async () => {
        const requests = Array(5).fill(null).map(() =>
          epClient.getMEPs({ limit: 1 })
        );

        const start = Date.now();
        await Promise.all(requests);
        const duration = Date.now() - start;

        // Should complete reasonably fast with caching
        expect(duration).toBeLessThan(5000);
      });
    });

### E2E Tests

Location: `tests/e2e/`

Purpose: Test complete MCP client-server flow

Example:

    // tests/e2e/mepQueries.e2e.test.ts
    import { describe, it, expect, beforeAll, afterAll } from 'vitest';
    import { Client } from '@modelcontextprotocol/sdk/client/index.js';
    import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

    describe('MEP Queries E2E', () => {
      let client: Client;
      let transport: StdioClientTransport;

      beforeAll(async () => {
        transport = new StdioClientTransport({
          command: 'node',
          args: ['dist/index.js']
        });

        client = new Client({
          name: 'test-client',
          version: '1.0.0'
        }, {
          capabilities: {}
        });

        await client.connect(transport);
      });

      afterAll(async () => {
        await client.close();
      });

      it('should list MEPs', async () => {
        const result = await client.callTool('get_meps', {
          country: 'SE',
          limit: 10
        });

        expect(result.content).toBeDefined();
        expect(result.content[0].type).toBe('text');

        const data = JSON.parse(result.content[0].text);
        expect(data.data).toBeDefined();
        expect(Array.isArray(data.data)).toBe(true);
      });
    });

### Performance Tests

Location: `tests/performance/`

Purpose: Verify performance requirements (<200ms cached)

Example:

    // tests/performance/benchmarks.test.ts
    import { describe, it, expect } from 'vitest';
    import { measureTime } from '../helpers/testUtils.js';

    describe('Performance Benchmarks', () => {
      it('should respond in <200ms for cached requests', async () => {
        // Warm cache
        await client.callTool('get_meps', { country: 'SE' });

        // Measure cached response
        const duration = await measureTime(() =>
          client.callTool('get_meps', { country: 'SE' })
        );

        expect(duration).toBeLessThan(200);
      });

      it('should handle 50 concurrent requests', async () => {
        const requests = Array(50).fill(null).map((_, i) =>
          client.callTool('get_meps', {
            country: 'SE',
            offset: i * 10,
            limit: 10
          })
        );

        const start = Date.now();
        await Promise.all(requests);
        const duration = Date.now() - start;

        // Should complete in reasonable time
        expect(duration).toBeLessThan(10000);
      });
    });

## 🎨 Code Style & Conventions

### TypeScript Guidelines

1. Use Strict Mode

    // tsconfig.json already enables strict mode
    {
      "compilerOptions": {
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "noUncheckedIndexedAccess": true
      }
    }

2. Explicit Return Types

    // ❌ Bad
    export async function getData(id: string) {
      return await fetch(`/api/${id}`);
    }

    // ✅ Good
    export async function getData(id: string): Promise<Response> {
      return await fetch(`/api/${id}`);
    }

3. No `any` Type

    // ❌ Bad
    function process(data: any) {
      return data.field;
    }

    // ✅ Good
    function process(data: unknown): string {
      if (typeof data === 'object' && data !== null && 'field' in data) {
        return String(data.field);
      }
      throw new Error('Invalid data structure');
    }

4. Use Branded Types

    // Good for IDs
    type MEPID = string & { readonly __brand: 'MEPID' };

    function getMEP(id: MEPID): Promise<MEP> {
      // ...
    }

    // Prevents accidental string mixing
    const id: MEPID = 'MEP-123' as MEPID;
    getMEP(id); // OK
    getMEP('random-string'); // ❌ Type error

### Naming Conventions

Type | Convention | Example
--- | --- | ---
Files | camelCase | `getMEPs.ts`
Classes | PascalCase | `RateLimiter`
Interfaces | PascalCase | `MEPDetails`
Functions | camelCase | `handleGetMEPs`
Constants | UPPER_SNAKE_CASE | `DEFAULT_LIMIT`
Private fields | _camelCase | `_cache`
Types | PascalCase | `ToolMetadata`

### Code Organization

1. Import Order

    // 1. External packages
    import { z } from 'zod';
    import { LRUCache } from 'lru-cache';

    // 2. Internal modules (absolute imports)
    import { epClient } from '../clients/europeanParliamentClient.js';
    import { MEPSchema } from '../schemas/europeanParliament.js';

    // 3. Types
    import type { MEP, Committee } from '../types/europeanParliament.js';

2. File Structure

    /**
     * File header comment
     */

    // Imports
    import { ... } from '...';

    // Constants
    const DEFAULT_LIMIT = 50;

    // Types/Interfaces
    interface LocalType {
      // ...
    }

    // Main implementation
    export async function mainFunction() {
      // ...
    }

    // Helper functions (private)
    function helperFunction() {
      // ...
    }

    // Exports
    export const metadata = {
      // ...
    };

### Error Handling

    // Always catch and sanitize errors
    try {
      const result = await externalAPI();
      return result;
    } catch (error) {
      // Log full error internally
      logger.error('External API failed', { error });

      // Throw sanitized error
      const message = error instanceof Error ? error.message : 'Unknown error';
      throw new Error(`Operation failed: ${message}`);
    }

### Documentation

JSDoc for Public APIs:

    /**
     * Retrieve Members of European Parliament with filters
     *
     * @param args - Tool arguments including country, group, committee filters
     * @returns MCP-compliant response with paginated MEP data
     * @throws {ValidationError} If input parameters are invalid
     * @throws {APIError} If European Parliament API request fails
     *
     * @example
     * ```typescript
     * const result = await handleGetMEPs({
     *   country: 'SE',
     *   group: 'S&D',
     *   limit: 20
     * });
     * ```
     *
     * @security
     * - Input validated with Zod schemas
     * - Rate limited to 100 requests per 15 minutes
     * - All requests logged for audit
     *
     * @performance
     * - Cached responses: <1ms
     * - API requests: ~150-200ms
     * - Cache TTL: 15 minutes
     */
    export async function handleGetMEPs(args: unknown): Promise<MCPResponse> {
      // Implementation
    }

## 🔒 Security Considerations

### Input Validation

Always use Zod schemas:

    // Define schema
    const InputSchema = z.object({
      id: z.string().min(1).max(100),
      date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
      keywords: z.string().regex(/^[a-zA-Z0-9\s\-_]+$/)
    });

    // Validate input
    export function handleTool(args: unknown) {
      const params = InputSchema.parse(args); // Throws on invalid
      // params is now type-safe
    }

### Output Validation

    // Validate API responses
    const OutputSchema = z.object({
      id: z.string(),
      name: z.string(),
      data: z.array(z.unknown())
    });

    const apiResponse = await fetch(...);
    const validated = OutputSchema.parse(apiResponse); // Ensure structure
    return validated;

### Error Sanitization

    // ❌ Bad - Exposes internal details
    catch (error) {
      throw error;
    }

    // ✅ Good - Sanitized error
    catch (error) {
      logger.error('Internal error', { error });
      throw new Error('Operation failed');
    }

### Rate Limiting

    // Always check rate limits
    if (!await rateLimiter.tryRemoveTokens(1)) {
      throw new Error('Rate limit exceeded');
    }

    // Make request
    const response = await fetch(...);

### Audit Logging

    // Log all data access
    logger.info('Accessed MEP data', {
      user: 'client-id',
      mepId: params.id,
      timestamp: new Date().toISOString()
    });

### Security Checklist

- [ ] Input validated with Zod schemas
- [ ] Output validated before returning
- [ ] Errors sanitized (no internal details exposed)
- [ ] Rate limiting applied
- [ ] Audit logging implemented
- [ ] No secrets in code or logs
- [ ] HTTPS only for external APIs
- [ ] GDPR compliance (data minimization)

## ⚡ Performance Guidelines

### Caching Strategy

    // Use LRU cache with appropriate TTL
    const cache = new LRUCache<string, any>({
      max: 500,              // Max entries
      ttl: 15 * 60 * 1000,  // 15 minutes
      allowStale: false
    });

    // Cache key strategy
    function getCacheKey(params: any): string {
      return `${method}:${JSON.stringify(params)}`;
    }

    // Check cache before API call
    const cached = cache.get(cacheKey);
    if (cached) {
      return cached;
    }

    // Fetch and cache
    const fresh = await fetchFromAPI();
    cache.set(cacheKey, fresh);
    return fresh;

### Async Best Practices

    // ❌ Bad - Sequential
    const meps = await getMEPs();
    const sessions = await getSessions();
    const votes = await getVotes();

    // ✅ Good - Parallel
    const [meps, sessions, votes] = await Promise.all([
      getMEPs(),
      getSessions(),
      getVotes()
    ]);

### Memory Management

    // Limit array sizes
    const results = largeArray.slice(0, 100); // Take first 100

    // Stream large datasets
    async function* streamData() {
      let offset = 0;
      while (true) {
        const batch = await fetchBatch(offset, 100);
        if (batch.length === 0) break;

        for (const item of batch) {
          yield item;
        }

        offset += batch.length;
      }
    }

## 🤝 Contributing Workflow

### 1. Fork and Clone

    # Fork on GitHub, then:
    git clone https://github.com/YOUR_USERNAME/European-Parliament-MCP-Server.git
    cd European-Parliament-MCP-Server
    git remote add upstream https://github.com/Hack23/European-Parliament-MCP-Server.git

### 2. Create Feature Branch

    git checkout -b feature/my-feature

Branch naming:

- `feature/` - New features
- `fix/` - Bug fixes
- `docs/` - Documentation
- `refactor/` - Code refactoring
- `test/` - Test improvements

### 3. Make Changes

Follow code style, add tests, update documentation.

### 4. Commit Changes

    git add .
    git commit -m "feat: add new MEP filtering option"

Commit message format (Conventional Commits):

    type(scope): description

    [optional body]

    [optional footer]

Types:

- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation
- `style`: Formatting
- `refactor`: Code restructuring
- `test`: Testing
- `chore`: Maintenance

Examples:

    feat(tools): add committee member filtering
    fix(cache): resolve cache invalidation bug
    docs(api): update get_meps examples
    test(e2e): add voting records integration test

### 5. Push and Create PR

    git push origin feature/my-feature

Then create Pull Request on GitHub.

### 6. PR Review Process

1. Automated Checks:

   - All tests pass
   - Linting passes
   - Coverage meets threshold (80%)
   - Security scan passes

2. Code Review:

   - At least one approval required
   - Security team review for security-critical changes
   - Documentation updated
   - CHANGELOG.md updated for user-facing changes

3. Merge:

   - Squash and merge to main
   - Delete branch after merge

## 📦 Release Process

### Versioning

Follow Semantic Versioning:

- Major (x.0.0): Breaking changes
- Minor (0.x.0): New features (backward compatible)
- Patch (0.0.x): Bug fixes (backward compatible)

### Automated Release

1. Create Release PR:

   - Update version in `package.json`
   - Update `CHANGELOG.md`
   - Commit: `chore(release): v1.2.0`

2. Merge to Main:

   - All tests pass
   - Approved by maintainers

3. GitHub Actions:

   - Automatically creates release
   - Generates release notes
   - Publishes to npm (if configured)
   - Creates git tag

### Manual Release

    # Update version
    npm version minor  # or major, patch

    # Push with tags
    git push origin main --follow-tags

    # Create GitHub release
    gh release create v1.2.0 \
      --title "v1.2.0" \
      --notes-file CHANGELOG.md

## 📚 Additional Resources

### Documentation

- API Usage Guide - Tool documentation
- Architecture Diagrams - Visual architecture
- Troubleshooting - Common issues
- Deployment Guide - Deployment instructions
- Performance Guide - Optimization strategies

### External Resources

- MCP Specification
- TypeScript Handbook
- Zod Documentation
- Vitest Documentation

### ISMS Compliance

- Hack23 ISMS Policies
- Secure Development Policy

Built with ❤️ by Hack23 AB
ISMS-compliant development demonstrating security excellence