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 | 4x 2x 2x 2x 2x 11x 11x 11x 1x 11x 1x 1x 1x 15x 15x 1x 1x 1x 14x 14x 14x 14x 14x 14x 12x 12x 1x 11x 2x 1x 4x | /**
* MCP Tool: get_external_documents_feed
*
* Get recently updated external documents (non-EP documents) from the feed.
*
* **EP API Endpoint:**
* - `GET /external-documents/feed`
*
* ISMS Policy: SC-002 (Input Validation), AC-003 (Least Privilege)
*/
import { GetExternalDocumentsFeedSchema } from '../schemas/europeanParliament.js';
import { epClient } from '../clients/europeanParliamentClient.js';
import { ToolError } from './shared/errors.js';
import { isUpstream404, isErrorInBody, buildFeedSuccessResponse, buildEmptyFeedResponse } from './shared/feedUtils.js';
import { buildToolResponse } from './shared/responseBuilder.js';
import { z } from 'zod';
import type { ToolResult } from './shared/types.js';
const EXTERNAL_DOCUMENTS_EMPTY_REASON =
'EP external-documents feed returned zero items for the requested window; this is ambiguous between a true empty window and feed freshness/ordering lag.';
interface ExternalDocumentsFeedDiagnostics {
endpoint: 'external-documents/feed';
requestedWindow: {
timeframe: string;
startDate?: string;
workType?: string;
};
emptyResultAmbiguity: 'true-empty-or-feed-freshness-lag';
freshnessStatus: 'unknown';
fallbackTool: 'get_external_documents';
fallbackArguments: {
limit: 20;
};
}
type ExternalDocumentsFeedParams = ReturnType<typeof GetExternalDocumentsFeedSchema.parse>;
function buildExternalDocumentsFeedDiagnostics(
params: ExternalDocumentsFeedParams
): ExternalDocumentsFeedDiagnostics {
const requestedWindow: ExternalDocumentsFeedDiagnostics['requestedWindow'] = {
timeframe: params.timeframe,
};
if (params.startDate !== undefined) requestedWindow.startDate = params.startDate;
if (params.workType !== undefined) requestedWindow.workType = params.workType;
return {
endpoint: 'external-documents/feed',
requestedWindow,
emptyResultAmbiguity: 'true-empty-or-feed-freshness-lag',
freshnessStatus: 'unknown',
fallbackTool: 'get_external_documents',
fallbackArguments: { limit: 20 },
};
}
function hasFeedItems(result: unknown): boolean {
const source = (result ?? {}) as Record<string, unknown>;
return Array.isArray(source['data']) && source['data'].length > 0;
}
function withEmptyFeedDiagnostics(result: unknown, params: ExternalDocumentsFeedParams): unknown {
if (hasFeedItems(result)) return result;
const source = (result ?? {}) as Record<string, unknown>;
// Error-in-body responses are upstream enrichment failures, not ambiguous
// true-empty/freshness-lag windows — do not attach diagnostics.
Iif (isErrorInBody(source)) return result;
return {
...source,
dataQualityDiagnostics: buildExternalDocumentsFeedDiagnostics(params),
};
}
function buildExternalDocumentsUnavailableResponse(params: ExternalDocumentsFeedParams): ToolResult {
const items: unknown[] = [];
return buildToolResponse({
status: 'unavailable',
generatedAt: new Date().toISOString(),
items,
itemCount: 0,
reason: EXTERNAL_DOCUMENTS_EMPTY_REASON,
data: items,
'@context': [],
dataQualityWarnings: [EXTERNAL_DOCUMENTS_EMPTY_REASON],
dataQualityDiagnostics: buildExternalDocumentsFeedDiagnostics(params),
});
}
/**
* Handles the get_external_documents_feed MCP tool request.
*
* @param args - Raw tool arguments, validated against {@link GetExternalDocumentsFeedSchema}
* @returns MCP tool result containing recently updated external document data
* @security Input is validated with Zod before any API call.
*/
export async function handleGetExternalDocumentsFeed(args: unknown): Promise<ToolResult> {
let params: ReturnType<typeof GetExternalDocumentsFeedSchema.parse>;
try {
params = GetExternalDocumentsFeedSchema.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_feed',
operation: 'validateInput',
message: `Invalid parameters: ${fieldErrors}`,
isRetryable: false,
cause: error,
});
}
throw error;
}
try {
const apiParams: Record<string, unknown> = {};
apiParams['timeframe'] = params.timeframe;
if (params.startDate !== undefined) apiParams['startDate'] = params.startDate;
if (params.workType !== undefined) apiParams['workType'] = params.workType;
const result = await epClient.getExternalDocumentsFeed(
apiParams
);
const source = result as Record<string, unknown>;
if (isErrorInBody(source)) {
return buildEmptyFeedResponse(
'EP API returned an error-in-body response for get_external_documents_feed — the upstream enrichment step may have failed.',
);
}
return buildFeedSuccessResponse(
withEmptyFeedDiagnostics(result, params),
[],
EXTERNAL_DOCUMENTS_EMPTY_REASON,
);
} catch (error: unknown) {
if (isUpstream404(error)) return buildExternalDocumentsUnavailableResponse(params);
throw new ToolError({
toolName: 'get_external_documents_feed',
operation: 'fetchData',
message: 'Failed to retrieve external documents feed',
isRetryable: true,
cause: error,
});
}
}
/** Tool metadata for get_external_documents_feed */
export const getExternalDocumentsFeedToolMetadata = {
name: 'get_external_documents_feed',
description:
'Get recently updated external documents (non-EP documents) from the feed. Returns external documents published or updated during the specified timeframe. Data source: European Parliament Open Data Portal.',
inputSchema: {
type: 'object' as const,
properties: {
timeframe: {
type: 'string',
description: 'Timeframe for the feed (today, one-day, one-week, one-month, custom)',
enum: ['today', 'one-day', 'one-week', 'one-month', 'custom'],
default: 'one-week',
},
startDate: {
type: 'string',
description: 'Start date (YYYY-MM-DD) — required when timeframe is "custom"',
},
workType: { type: 'string', description: 'Work type filter' },
},
},
};
|