All files / src/tools getSpeeches.ts

93.75% Statements 15/16
75% Branches 3/4
100% Functions 2/2
93.33% Lines 14/15

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 119 120 121 122 123 124 125 126 127 128 129                                                                                                                      16x 16x   5x 5x 5x                     11x 11x 1x 1x     10x                   10x       9x   1x                   4x                                          
/**
 * MCP Tool: get_speeches
 *
 * Retrieve European Parliament plenary speeches and speech-related activities.
 * Supports single speech lookup by speechId or list with date range filtering.
 *
 * **Intelligence Perspective:** Speech data enables content analysis of MEP positions,
 * rhetorical patterns, and policy priorities across plenary debates.
 *
 * **Business Perspective:** Speech transcripts power NLP-based products, sentiment
 * analysis dashboards, and topic monitoring services.
 *
 * **EP API Endpoints:**
 * - `GET /speeches` (list)
 * - `GET /speeches/{speech-id}` (single)
 *
 * ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
 */
 
import { GetSpeechesSchema } from '../schemas/europeanParliament.js';
import { epClient } from '../clients/europeanParliamentClient.js';
import { buildToolResponse } from './shared/responseBuilder.js';
import { buildApiParams } from './shared/paramBuilder.js';
import { ToolError } from './shared/errors.js';
import { z } from 'zod';
import type { ToolResult } from './shared/types.js';
 
/**
 * Handles the get_speeches MCP tool request.
 *
 * Retrieves European Parliament plenary speeches and debate contributions.
 * Supports single speech lookup by speechId or a filtered list by date range.
 *
 * @param args - Raw tool arguments, validated against {@link GetSpeechesSchema}
 * @returns MCP tool result containing either a single speech record or a paginated list of speeches
 * @throws - If `args` fails schema validation (e.g., missing required fields or invalid format)
 * - If the European Parliament API is unreachable or returns an error response
 *
 * @example
 * ```typescript
 * // Single speech lookup
 * const result = await handleGetSpeeches({ speechId: 'SPEECH-2024-001' });
 * // Returns the full record for the specified speech
 *
 * // List speeches with date filter
 * const list = await handleGetSpeeches({ dateFrom: '2024-01-01', dateTo: '2024-03-31', limit: 50 });
 * // Returns up to 50 speeches from Q1 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 getSpeechesToolMetadata} for MCP schema registration
 * @see {@link handleGetMeetingActivities} for retrieving broader meeting-level activities
 */
export async function handleGetSpeeches(args: unknown): Promise<ToolResult> {
  // Validate input — ZodErrors here are client mistakes (non-retryable)
  let params: ReturnType<typeof GetSpeechesSchema.parse>;
  try {
    params = GetSpeechesSchema.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_speeches',
        operation: 'validateInput',
        message: `Invalid parameters: ${fieldErrors}`,
        isRetryable: false,
        cause: error,
      });
    }
    throw error;
  }
 
  try {
    if (params.speechId !== undefined) {
      const result = await epClient.getSpeechById(params.speechId);
      return buildToolResponse(result);
    }
 
    const apiParams = {
      limit: params.limit,
      offset: params.offset,
      ...buildApiParams(params, [
        { from: 'year', to: 'year' },
        { from: 'dateFrom', to: 'dateFrom' },
        { from: 'dateTo', to: 'dateTo' },
      ]),
    };
 
    const result = await epClient.getSpeeches(
      apiParams as Parameters<typeof epClient.getSpeeches>[0]
    );
 
    return buildToolResponse(result);
  } catch (error: unknown) {
    throw new ToolError({
      toolName: 'get_speeches',
      operation: 'fetchData',
      message: 'Failed to retrieve speeches',
      isRetryable: true,
      cause: error,
    });
  }
}
/** Tool metadata for get_speeches */
export const getSpeechesToolMetadata = {
  name: 'get_speeches',
  description:
    'Get European Parliament plenary speeches and debate contributions. Supports single speech lookup by speechId or list with year or date range filtering. Data source: European Parliament Open Data Portal.',
  inputSchema: {
    type: 'object' as const,
    properties: {
      speechId: { type: 'string', description: 'Speech ID for single speech lookup' },
      year: {
        type: 'number',
        description: 'Filter by calendar year (recommended for annual counts)',
        minimum: 1900,
        maximum: 2100,
      },
      dateFrom: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
      dateTo: { type: 'string', description: 'End date (YYYY-MM-DD)' },
      limit: { type: 'number', description: 'Maximum results to return (1-100)', default: 50 },
      offset: { type: 'number', description: 'Pagination offset', default: 0 },
    },
  },
};