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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | 5x 5x 5x 22x 22x 5x 5x 5x 17x 17x 17x 13x 13x 13x 4x 4x 4x | /**
* MCP Tool: get_voting_records
*
* Retrieve voting records from European Parliament plenary sessions
*
* **Intelligence Perspective:** Core intelligence product for voting pattern analysis,
* political group cohesion measurement, cross-party alliance detection, and MEP
* loyalty/independence scoring through structured analytic techniques.
*
* **Business Perspective:** High-value data product for political risk assessment firms,
* policy analysis consultancies, and corporate government affairs departments.
*
* **Marketing Perspective:** Most compelling data for data journalism partnerships,
* academic research collaborations, and transparency advocacy organizations.
*
* ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
*/
import { GetVotingRecordsSchema, VotingRecordSchema, PaginatedResponseSchema } 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';
/**
* Format a list of Zod issues into a single human-readable string.
*
* Root-level issues (e.g. `unrecognized_keys` produced by `.strict()`) have
* an empty `path` array; rendering them as `path.join('.')` would yield
* `: message` with a misleading leading colon. We substitute `(root)` for
* such issues so the error reads `(root): Unrecognized key(s) ...`.
*/
function formatZodIssues(error: z.ZodError): string {
return error.issues
.map(issue => {
const pathStr = issue.path.length > 0 ? issue.path.join('.') : '(root)';
return `${pathStr}: ${issue.message}`;
})
.join('; ');
}
/**
* Handles the get_voting_records MCP tool request.
*
* Retrieves voting records from European Parliament plenary sessions, supporting
* filtering by session, topic, and date range. Returns aggregate vote tallies
* (for/against/abstain) and final results. The EP API only provides aggregate
* vote counts, not individual MEP positions.
*
* @param args - Raw tool arguments, validated against {@link GetVotingRecordsSchema}
* @returns MCP tool result containing a paginated list of voting records with vote counts and results
* @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
* const result = await handleGetVotingRecords({
* sessionId: 'PLENARY-2024-01',
* topic: 'Climate Change',
* limit: 20
* });
* // Returns voting records for the January 2024 plenary session on climate topics
* ```
*
* @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 getVotingRecordsToolMetadata} for MCP schema registration
* @see [handleGetMeetingDecisions](../../getMeetingDecisions/functions/handleGetMeetingDecisions.md) for retrieving decisions linked to a specific sitting
*/
export async function handleGetVotingRecords(
args: unknown
): Promise<ToolResult> {
let params: ReturnType<typeof GetVotingRecordsSchema.parse>;
try {
params = GetVotingRecordsSchema.parse(args);
} catch (error: unknown) {
Eif (error instanceof z.ZodError) {
const fieldErrors = formatZodIssues(error);
throw new ToolError({
toolName: 'get_voting_records',
operation: 'validateInput',
message: `Invalid parameters: ${fieldErrors}`,
isRetryable: false,
cause: error,
});
}
throw error;
}
try {
const apiParams = {
limit: params.limit,
offset: params.offset,
...buildApiParams(params, [
{ from: 'sessionId', to: 'sessionId' },
{ from: 'topic', to: 'topic' },
{ from: 'dateFrom', to: 'dateFrom' },
{ from: 'dateTo', to: 'dateTo' },
]),
};
const result = await epClient.getVotingRecords(apiParams);
const outputSchema = PaginatedResponseSchema(VotingRecordSchema);
const validated = outputSchema.parse(result);
return buildToolResponse(validated);
} catch (error: unknown) {
Iif (error instanceof z.ZodError) {
const fieldErrors = formatZodIssues(error);
throw new ToolError({
toolName: 'get_voting_records',
operation: 'validateOutput',
message: `Unexpected EP API response format: ${fieldErrors}`,
isRetryable: false,
cause: error,
});
}
throw new ToolError({
toolName: 'get_voting_records',
operation: 'fetchData',
message: 'Failed to retrieve voting records',
isRetryable: true,
cause: error,
});
}
}
/**
* Tool metadata for MCP registration
*/
export const getVotingRecordsToolMetadata = {
name: 'get_voting_records',
description: 'Retrieve voting records from European Parliament plenary sessions. Filter by session, topic, or date range. Returns aggregate vote counts (for/against/abstain) and final result. The EP API only provides aggregate vote tallies, not individual MEP positions. NOTE: The EP publishes roll-call voting data with a delay of several weeks, so queries for the most recent 1-2 months may return empty results — this is expected EP API behavior, not an error.',
inputSchema: {
type: 'object' as const,
properties: {
sessionId: {
type: 'string',
description: 'Plenary session identifier',
minLength: 1,
maxLength: 100
},
topic: {
type: 'string',
description: 'Vote topic or keyword to search',
minLength: 1,
maxLength: 200
},
dateFrom: {
type: 'string',
description: 'Start date filter (YYYY-MM-DD format)',
pattern: '^\\d{4}-\\d{2}-\\d{2}$'
},
dateTo: {
type: 'string',
description: 'End date filter (YYYY-MM-DD format)',
pattern: '^\\d{4}-\\d{2}-\\d{2}$'
},
limit: {
type: 'number',
description: 'Maximum number of results to return (1-100)',
minimum: 1,
maximum: 100,
default: 50
},
offset: {
type: 'number',
description: 'Pagination offset',
minimum: 0,
default: 0
}
}
}
};
|