All files / src/tools getExternalDocuments.ts

94.44% Statements 17/18
83.33% Branches 5/6
100% Functions 2/2
93.75% Lines 15/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118                                                                                                                      18x 18x   5x 5x 5x                     13x 13x 3x 2x     10x       10x   10x       9x   2x                   4x                            
/**
 * MCP Tool: get_external_documents
 *
 * Retrieve external documents (non-EP documents) from the European Parliament data portal,
 * with optional single document lookup.
 *
 * **Intelligence Perspective:** External documents include inter-institutional communications,
 * Council positions, and Commission proposals relevant to EP legislative work.
 *
 * **Business Perspective:** Provides access to external reference documents needed
 * for complete regulatory intelligence.
 *
 * **EP API Endpoints:**
 * - `GET /external-documents` (list)
 * - `GET /external-documents/{doc-id}` (single)
 *
 * ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
 */
 
import { GetExternalDocumentsSchema } from '../schemas/europeanParliament.js';
import { epClient } from '../clients/europeanParliamentClient.js';
import { buildToolResponse } from './shared/responseBuilder.js';
import { ToolError } from './shared/errors.js';
import { z } from 'zod';
import type { ToolResult } from './shared/types.js';
 
/**
 * Handles the get_external_documents MCP tool request.
 *
 * Retrieves external documents (non-EP documents such as Council positions and Commission
 * proposals) from the European Parliament data portal. Supports both a paginated list view
 * and a single-document lookup when `docId` is provided.
 *
 * @param args - Raw tool arguments, validated against {@link GetExternalDocumentsSchema}
 * @returns MCP tool result containing external document data (single document or paginated list)
 * @throws - If `args` fails schema validation (e.g., invalid field types or formats)
 * - If the European Parliament API is unreachable or returns an error response
 *
 * @example
 * ```typescript
 * // Single document lookup
 * const single = await handleGetExternalDocuments({ docId: 'COM-2024-123' });
 * // Returns the external document with ID COM-2024-123
 *
 * // List documents filtered by year
 * const list = await handleGetExternalDocuments({ year: 2024, limit: 30, offset: 0 });
 * // Returns up to 30 external documents from 2024
 * ```
 *
 * @security - Input is validated with Zod before any API call.
 * - Personal data in responses is minimised per GDPR Article 5(1)(c).
 * - All requests are rate-limited and audit-logged per ISMS Policy AU-002.
 * @since 0.8.0
 * @see {@link getExternalDocumentsToolMetadata} for MCP schema registration
 * @see {@link handleSearchDocuments} for full-text search across EP legislative documents
 */
export async function handleGetExternalDocuments(args: unknown): Promise<ToolResult> {
  // Validate input — ZodErrors here are client mistakes (non-retryable)
  let params: ReturnType<typeof GetExternalDocumentsSchema.parse>;
  try {
    params = GetExternalDocumentsSchema.parse(args);
  } catch (error: unknown) {
    Eif (error instanceof z.ZodError) {
      const fieldErrors = error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ');
      throw new ToolError({
        toolName: 'get_external_documents',
        operation: 'validateInput',
        message: `Invalid parameters: ${fieldErrors}`,
        isRetryable: false,
        cause: error,
      });
    }
    throw error;
  }
 
  try {
    if (params.docId !== undefined) {
      const result = await epClient.getExternalDocumentById(params.docId);
      return buildToolResponse(result);
    }
 
    const apiParams: Record<string, unknown> = {
      limit: params.limit,
      offset: params.offset,
    };
    if (params.year !== undefined) apiParams['year'] = params.year;
 
    const result = await epClient.getExternalDocuments(
      apiParams as Parameters<typeof epClient.getExternalDocuments>[0]
    );
 
    return buildToolResponse(result);
  } catch (error: unknown) {
    throw new ToolError({
      toolName: 'get_external_documents',
      operation: 'fetchData',
      message: 'Failed to retrieve external documents',
      isRetryable: true,
      cause: error,
    });
  }
}
/** Tool metadata for get_external_documents */
export const getExternalDocumentsToolMetadata = {
  name: 'get_external_documents',
  description:
    'Get external documents (non-EP documents such as Council positions, Commission proposals) from the European Parliament data portal. Supports single document lookup by docId. Data source: European Parliament Open Data Portal.',
  inputSchema: {
    type: 'object' as const,
    properties: {
      docId: { type: 'string', description: 'Document ID for single document lookup' },
      year: { type: 'number', description: 'Filter by year' },
      limit: { type: 'number', description: 'Maximum results to return (1-100)', default: 50 },
      offset: { type: 'number', description: 'Pagination offset', default: 0 },
    },
  },
};