All files / src/tools getMeetingPlenarySessionDocuments.ts

91.66% Statements 11/12
50% Branches 1/2
100% Functions 2/2
90.9% Lines 10/11

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                                                                                                                                                16x 16x   7x 7x 7x                     9x 9x         8x   1x                   4x                            
/**
 * MCP Tool: get_meeting_plenary_session_documents
 *
 * Retrieve plenary session documents linked to a specific EP meeting (plenary sitting).
 *
 * **Intelligence Perspective:** Provides access to all session documents
 * associated with a specific plenary meeting, enabling comprehensive document
 * review and legislative tracking.
 *
 * **Business Perspective:** Enables stakeholders and lobbyists to retrieve
 * official session documents ahead of plenary votes for briefing preparation
 * and compliance monitoring.
 *
 * **Marketing Perspective:** Demonstrates direct access to official EP plenary
 * documents, appealing to legal professionals, journalists, and civic tech
 * developers who need reliable legislative source material.
 *
 * **EP API Endpoint:** `GET /meetings/{sitting-id}/plenary-session-documents`
 *
 * ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
 */
 
import { GetMeetingPlenarySessionDocumentsSchema } 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';
 
/**
 * Handle the `get_meeting_plenary_session_documents` MCP tool request.
 *
 * Retrieves all plenary session documents associated with a specific EP plenary
 * sitting by calling `GET /meetings/{sitting-id}/plenary-session-documents` via
 * {@link epClient}. The raw API response is normalised into a standardised
 * {@link ToolResult} using {@link buildToolResponse}.
 *
 * @param args - Raw tool arguments provided by the MCP client. Must conform to
 *   {@link GetMeetingPlenarySessionDocumentsSchema}:
 *   - `sittingId` (string, required): EP plenary sitting identifier.
 *   - `limit` (number, optional): Maximum results to return (1–100, default 20).
 *   - `offset` (number, optional): Pagination offset (default 0).
 * @returns A promise that resolves to an MCP {@link ToolResult} containing the
 *   plenary session documents for the requested sitting.
 * @throws If `args` fails {@link GetMeetingPlenarySessionDocumentsSchema} validation
 *   (e.g. missing required `sittingId` or out-of-range `limit`).
 * @throws If `sittingId` contains path-traversal characters (`.`, `\`, `?`, `#`)
 *   — the underlying client throws an `APIError(400)`.
 * @throws If the European Parliament API is unreachable or returns an error response.
 *
 * @example
 * ```typescript
 * const result = await handleGetMeetingPlenarySessionDocuments({
 *   sittingId: 'PV-9-2024-04-22',
 *   limit: 20,
 *   offset: 0
 * });
 * // Returns plenary session documents for sitting PV-9-2024-04-22
 * ```
 *
 * @security Input is validated with Zod before any API call.
 *   `sittingId` is checked against path-traversal characters before URL construction.
 *   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 1.0.0
 * @see {@link getMeetingPlenarySessionDocumentsToolMetadata} for MCP schema registration
 * @see {@link handleGetMeetingPlenarySessionDocumentItems} for individual agenda-item documents
 * @see {@link handleGetMeetingForeseenActivities} for planned meeting activities
 */
export async function handleGetMeetingPlenarySessionDocuments(args: unknown): Promise<ToolResult> {
  // Validate input — ZodErrors here are client mistakes (non-retryable)
  let params: ReturnType<typeof GetMeetingPlenarySessionDocumentsSchema.parse>;
  try {
    params = GetMeetingPlenarySessionDocumentsSchema.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_meeting_plenary_session_documents',
        operation: 'validateInput',
        message: `Invalid parameters: ${fieldErrors}`,
        isRetryable: false,
        cause: error,
      });
    }
    throw error;
  }
 
  try {
    const result = await epClient.getMeetingPlenarySessionDocuments(params.sittingId, {
      limit: params.limit,
      offset: params.offset,
    });
 
    return buildToolResponse(result);
  } catch (error: unknown) {
    throw new ToolError({
      toolName: 'get_meeting_plenary_session_documents',
      operation: 'fetchData',
      message: 'Failed to retrieve meeting plenary session documents',
      isRetryable: true,
      cause: error,
    });
  }
}
/** Tool metadata for get_meeting_plenary_session_documents */
export const getMeetingPlenarySessionDocumentsToolMetadata = {
  name: 'get_meeting_plenary_session_documents',
  description:
    'Get plenary session documents for a specific EP meeting/plenary sitting. Returns session documents associated with the meeting. Note: this endpoint can be slower than decisions; use a smaller limit for faster responses. Data source: European Parliament Open Data Portal.',
  inputSchema: {
    type: 'object' as const,
    properties: {
      sittingId: { type: 'string', description: 'Meeting / sitting identifier (required)' },
      limit: { type: 'number', description: 'Maximum results to return (1-100, default 20)', default: 20 },
      offset: { type: 'number', description: 'Pagination offset', default: 0 },
    },
    required: ['sittingId'],
  },
};