euparliamentmonitor 0.9.19 → 0.9.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +2 -2
  2. package/package.json +4 -3
  3. package/scripts/aggregator/editorial-brief-resolver.d.ts +38 -0
  4. package/scripts/aggregator/editorial-brief-resolver.js +32 -0
  5. package/scripts/aggregator/generator/render-one.js +35 -0
  6. package/scripts/aggregator/html/localize-body.d.ts +32 -0
  7. package/scripts/aggregator/html/localize-body.js +69 -0
  8. package/scripts/aggregator/html/shell.d.ts +10 -0
  9. package/scripts/aggregator/html/shell.js +11 -1
  10. package/scripts/aggregator/markdown-renderer.d.ts +23 -24
  11. package/scripts/aggregator/markdown-renderer.js +39 -25
  12. package/scripts/aggregator/metadata/artifact-highlight.d.ts +15 -22
  13. package/scripts/aggregator/metadata/artifact-highlight.js +14 -230
  14. package/scripts/aggregator/metadata/artifact-walker.d.ts +34 -0
  15. package/scripts/aggregator/metadata/artifact-walker.js +177 -0
  16. package/scripts/aggregator/metadata/editorial-highlight.d.ts +15 -0
  17. package/scripts/aggregator/metadata/editorial-highlight.js +53 -0
  18. package/scripts/aggregator/metadata/priority-finding-highlight.js +7 -2
  19. package/scripts/aggregator/metadata/resolve-helpers.js +9 -3
  20. package/scripts/aggregator/metadata/text-utils.js +7 -0
  21. package/scripts/aggregator/metadata/translated-sibling.d.ts +23 -0
  22. package/scripts/aggregator/metadata/translated-sibling.js +39 -0
  23. package/scripts/aggregator/reader-guide/builder.js +3 -1
  24. package/scripts/aggregator/reader-guide/labels.d.ts +7 -0
  25. package/scripts/aggregator/reader-guide/labels.js +22 -0
  26. package/scripts/aggregator/reader-intelligence-guide.d.ts +1 -1
  27. package/scripts/aggregator/reader-intelligence-guide.js +1 -1
  28. package/scripts/aggregator/seo-entity-extractor.d.ts +45 -0
  29. package/scripts/aggregator/seo-entity-extractor.js +211 -0
  30. package/scripts/constants/articles/breaking-strings-central.d.ts +8 -0
  31. package/scripts/constants/articles/breaking-strings-central.js +105 -0
  32. package/scripts/constants/articles/breaking-strings-east.d.ts +8 -0
  33. package/scripts/constants/articles/breaking-strings-east.js +203 -0
  34. package/scripts/constants/articles/breaking-strings-nordic.d.ts +8 -0
  35. package/scripts/constants/articles/breaking-strings-nordic.js +252 -0
  36. package/scripts/constants/articles/breaking-strings-west.d.ts +8 -0
  37. package/scripts/constants/articles/breaking-strings-west.js +154 -0
  38. package/scripts/constants/articles/breaking.d.ts +0 -1
  39. package/scripts/constants/articles/breaking.js +9 -6
  40. package/scripts/constants/articles/dashboard/ar.d.ts +8 -0
  41. package/scripts/constants/articles/dashboard/ar.js +71 -0
  42. package/scripts/constants/articles/dashboard/da.d.ts +8 -0
  43. package/scripts/constants/articles/dashboard/da.js +71 -0
  44. package/scripts/constants/articles/dashboard/de.d.ts +8 -0
  45. package/scripts/constants/articles/dashboard/de.js +71 -0
  46. package/scripts/constants/articles/dashboard/en.d.ts +8 -0
  47. package/scripts/constants/articles/dashboard/en.js +71 -0
  48. package/scripts/constants/articles/dashboard/es.d.ts +8 -0
  49. package/scripts/constants/articles/dashboard/es.js +71 -0
  50. package/scripts/constants/articles/dashboard/fi.d.ts +8 -0
  51. package/scripts/constants/articles/dashboard/fi.js +71 -0
  52. package/scripts/constants/articles/dashboard/fr.d.ts +8 -0
  53. package/scripts/constants/articles/dashboard/fr.js +71 -0
  54. package/scripts/constants/articles/dashboard/he.d.ts +8 -0
  55. package/scripts/constants/articles/dashboard/he.js +71 -0
  56. package/scripts/constants/articles/dashboard/index.d.ts +7 -0
  57. package/scripts/constants/articles/dashboard/index.js +33 -0
  58. package/scripts/constants/articles/dashboard/ja.d.ts +8 -0
  59. package/scripts/constants/articles/dashboard/ja.js +71 -0
  60. package/scripts/constants/articles/dashboard/ko.d.ts +8 -0
  61. package/scripts/constants/articles/dashboard/ko.js +71 -0
  62. package/scripts/constants/articles/dashboard/nl.d.ts +8 -0
  63. package/scripts/constants/articles/dashboard/nl.js +71 -0
  64. package/scripts/constants/articles/dashboard/no.d.ts +8 -0
  65. package/scripts/constants/articles/dashboard/no.js +71 -0
  66. package/scripts/constants/articles/dashboard/sv.d.ts +8 -0
  67. package/scripts/constants/articles/dashboard/sv.js +71 -0
  68. package/scripts/constants/articles/dashboard/zh.d.ts +8 -0
  69. package/scripts/constants/articles/dashboard/zh.js +71 -0
  70. package/scripts/constants/articles/dashboard.d.ts +7 -2
  71. package/scripts/constants/articles/dashboard.js +4 -8
  72. package/scripts/constants/articles/deep-analysis/ar.d.ts +8 -0
  73. package/scripts/constants/articles/deep-analysis/ar.js +75 -0
  74. package/scripts/constants/articles/deep-analysis/da.d.ts +8 -0
  75. package/scripts/constants/articles/deep-analysis/da.js +75 -0
  76. package/scripts/constants/articles/deep-analysis/de.d.ts +8 -0
  77. package/scripts/constants/articles/deep-analysis/de.js +75 -0
  78. package/scripts/constants/articles/deep-analysis/en.d.ts +8 -0
  79. package/scripts/constants/articles/deep-analysis/en.js +75 -0
  80. package/scripts/constants/articles/deep-analysis/es.d.ts +8 -0
  81. package/scripts/constants/articles/deep-analysis/es.js +75 -0
  82. package/scripts/constants/articles/deep-analysis/fi.d.ts +8 -0
  83. package/scripts/constants/articles/deep-analysis/fi.js +75 -0
  84. package/scripts/constants/articles/deep-analysis/fr.d.ts +8 -0
  85. package/scripts/constants/articles/deep-analysis/fr.js +75 -0
  86. package/scripts/constants/articles/deep-analysis/he.d.ts +8 -0
  87. package/scripts/constants/articles/deep-analysis/he.js +75 -0
  88. package/scripts/constants/articles/deep-analysis/index.d.ts +7 -0
  89. package/scripts/constants/articles/deep-analysis/index.js +33 -0
  90. package/scripts/constants/articles/deep-analysis/ja.d.ts +8 -0
  91. package/scripts/constants/articles/deep-analysis/ja.js +75 -0
  92. package/scripts/constants/articles/deep-analysis/ko.d.ts +8 -0
  93. package/scripts/constants/articles/deep-analysis/ko.js +75 -0
  94. package/scripts/constants/articles/deep-analysis/nl.d.ts +8 -0
  95. package/scripts/constants/articles/deep-analysis/nl.js +75 -0
  96. package/scripts/constants/articles/deep-analysis/no.d.ts +8 -0
  97. package/scripts/constants/articles/deep-analysis/no.js +75 -0
  98. package/scripts/constants/articles/deep-analysis/sv.d.ts +8 -0
  99. package/scripts/constants/articles/deep-analysis/sv.js +75 -0
  100. package/scripts/constants/articles/deep-analysis/zh.d.ts +8 -0
  101. package/scripts/constants/articles/deep-analysis/zh.js +75 -0
  102. package/scripts/constants/articles/deep-analysis.d.ts +4 -3
  103. package/scripts/constants/articles/deep-analysis.js +3 -7
  104. package/scripts/constants/articles/localized-keywords-central.d.ts +8 -0
  105. package/scripts/constants/articles/localized-keywords-central.js +118 -0
  106. package/scripts/constants/articles/localized-keywords-nordic.d.ts +8 -0
  107. package/scripts/constants/articles/localized-keywords-nordic.js +303 -0
  108. package/scripts/constants/articles/localized-keywords.js +4 -2
  109. package/scripts/constants/articles/swot-builder-central.d.ts +8 -0
  110. package/scripts/constants/articles/swot-builder-central.js +90 -0
  111. package/scripts/constants/articles/swot-builder-nordic.d.ts +8 -0
  112. package/scripts/constants/articles/swot-builder-nordic.js +216 -0
  113. package/scripts/constants/articles/swot.js +4 -2
  114. package/scripts/constants/articles/week-ahead-eu.d.ts +12 -0
  115. package/scripts/constants/articles/week-ahead-eu.js +278 -0
  116. package/scripts/constants/articles/week-ahead-global.d.ts +12 -0
  117. package/scripts/constants/articles/week-ahead-global.js +278 -0
  118. package/scripts/constants/articles/week-ahead.d.ts +4 -7
  119. package/scripts/constants/articles/week-ahead.js +11 -535
  120. package/scripts/constants/world-bank/category-map-analysis.d.ts +9 -0
  121. package/scripts/constants/world-bank/category-map-analysis.js +204 -0
  122. package/scripts/constants/world-bank/category-map-legislative.d.ts +9 -0
  123. package/scripts/constants/world-bank/category-map-legislative.js +130 -0
  124. package/scripts/constants/world-bank/category-map-periodic.d.ts +9 -0
  125. package/scripts/constants/world-bank/category-map-periodic.js +176 -0
  126. package/scripts/constants/world-bank/category-map.d.ts +3 -26
  127. package/scripts/constants/world-bank/category-map.js +8 -501
  128. package/scripts/discover-untranslated-briefs.js +123 -4
  129. package/scripts/generators/news-indexes/per-language.js +21 -7
  130. package/scripts/generators/political-intelligence/html.js +39 -8
  131. package/scripts/generators/sitemap/html.js +25 -7
  132. package/scripts/mcp/ep/client.d.ts +0 -1
  133. package/scripts/mcp/ep/client.js +0 -65
  134. package/scripts/mcp/ep/error-classifier.d.ts +2 -2
  135. package/scripts/mcp/ep/error-classifier.js +2 -2
  136. package/scripts/mcp/ep/tools-list.d.ts +13 -0
  137. package/scripts/mcp/ep/tools-list.js +79 -0
  138. package/scripts/mcp/ep-mcp-client.d.ts +1 -0
  139. package/scripts/mcp/ep-mcp-client.js +1 -0
  140. package/scripts/mcp/imf/client.d.ts +3 -64
  141. package/scripts/mcp/imf/client.js +18 -207
  142. package/scripts/mcp/imf/http-transport.d.ts +92 -0
  143. package/scripts/mcp/imf/http-transport.js +232 -0
  144. package/scripts/mcp/transport/connection.d.ts +25 -53
  145. package/scripts/mcp/transport/connection.js +90 -250
  146. package/scripts/mcp/transport/process.d.ts +62 -0
  147. package/scripts/mcp/transport/process.js +147 -0
  148. package/scripts/mcp/transport/reconnect.d.ts +73 -0
  149. package/scripts/mcp/transport/reconnect.js +96 -0
  150. package/scripts/validate-brief-translations.js +122 -6
  151. package/scripts/constants/articles/breaking-strings-eu.d.ts +0 -7
  152. package/scripts/constants/articles/breaking-strings-global.d.ts +0 -7
  153. package/scripts/constants/articles/dashboard-builder-eu.d.ts +0 -7
  154. package/scripts/constants/articles/dashboard-builder-global.d.ts +0 -7
  155. package/scripts/constants/articles/deep-analysis-strings-eu.d.ts +0 -7
  156. package/scripts/constants/articles/deep-analysis-strings-global.d.ts +0 -7
  157. package/scripts/constants/articles/localized-keywords-eu.d.ts +0 -7
  158. package/scripts/constants/articles/swot-builder-eu.d.ts +0 -7
@@ -77,40 +77,34 @@ export declare class MCPConnection {
77
77
  connected: boolean;
78
78
  };
79
79
  /**
80
- * Compute the delay before the next connection attempt.
81
- * Respects `Retry-After` carried by {@link MCPRateLimitError}; otherwise uses
82
- * exponential back-off (`connectionRetryDelay * 2^(attempt - 1)`).
83
- *
84
- * @param error - The error from the failed attempt
85
- * @param attempt - Number of attempts made so far (1-indexed)
86
- * @returns Delay in milliseconds
80
+ * Connect to the MCP server with retry logic
87
81
  */
88
- private _computeConnectionDelay;
82
+ connect(): Promise<void>;
89
83
  /**
90
- * Handle a single connection attempt error: re-throw immediately for non-retriable errors
91
- * (e.g. session expiry), increment the attempt counter, and return the delay to wait
92
- * before the next attempt. Throws when the maximum attempts have been exhausted.
84
+ * Advance the attempt counter and compute the next retry delay.
85
+ * Throws immediately for non-retriable errors or when exhausted.
93
86
  *
94
87
  * @param error - The error from the failed attempt
95
88
  * @returns Delay in milliseconds to wait before the next attempt
96
89
  */
97
- private _handleConnectionAttemptError;
90
+ private _nextConnectionDelay;
98
91
  /**
99
- * Connect to the MCP server with retry logic
92
+ * Build a {@link GatewayContext} adapter for gateway.ts helpers.
93
+ *
94
+ * @returns Context adapter for gateway.ts helpers
100
95
  */
101
- connect(): Promise<void>;
96
+ private _gatewayContext;
102
97
  /**
103
- * Build a {@link GatewayContext} adapter for delegating to gateway helpers.
98
+ * Build a {@link SpawnContext} adapter for process.ts spawn helpers.
104
99
  *
105
- * @returns A context adapter exposing the connection-level fields the
106
- * gateway helpers need (URL, API key, session ID accessors, request-ID
107
- * counter, connection-state setter).
100
+ * @returns Context adapter for stdio spawn helpers
108
101
  */
109
- private _gatewayContext;
110
- /** Attempt a single connection via MCP Gateway (HTTP transport). */
111
- private _attemptGatewayConnection;
102
+ private _spawnContext;
112
103
  /**
113
- * Attempt a single connection via stdio (spawns server binary)
104
+ * Attempt a single connection via stdio (spawns server binary).
105
+ * Delegates to {@link attemptStdioConnection} in process.ts.
106
+ *
107
+ * @returns Resolves when the spawn has completed (or rejects on spawn failure)
114
108
  */
115
109
  private _attemptConnection;
116
110
  /**
@@ -118,20 +112,12 @@ export declare class MCPConnection {
118
112
  */
119
113
  disconnect(): void;
120
114
  /**
121
- * Handle incoming messages from MCP server (stdio mode only)
115
+ * Handle incoming messages from MCP server (stdio mode only).
116
+ * Delegates to {@link handleIncomingMessage} in process.ts.
122
117
  *
123
118
  * @param line - JSON message line from server
124
119
  */
125
120
  handleMessage(line: string): void;
126
- /**
127
- * Send a request via MCP Gateway (HTTP transport). Delegates to the
128
- * gateway helper module.
129
- *
130
- * @param method - RPC method name
131
- * @param params - Method parameters
132
- * @returns Server response result payload
133
- */
134
- private _sendGatewayRequest;
135
121
  /**
136
122
  * Send a request to the MCP server
137
123
  *
@@ -155,32 +141,18 @@ export declare class MCPConnection {
155
141
  */
156
142
  callTool(name: string, args?: object): Promise<MCPToolResult>;
157
143
  /**
158
- * Attempt to reconnect to the MCP server with exponential back-off.
159
- * Concurrent callers await the same in-flight reconnect instead of no-oping,
160
- * ensuring the connection is re-established before all waiting callers continue.
161
- *
162
- * @returns Promise that resolves when reconnection succeeds or all attempts are exhausted
163
- */
164
- private reconnect;
165
- /**
166
- * Internal reconnect helper.
167
- *
168
- * Waits for an exponential back-off delay derived from the current
169
- * `reconnectCount`, then delegates to `connect()` which handles its own
170
- * retry loop. This avoids composing N×N attempts.
144
+ * Build a {@link ReconnectOps} adapter for reconnect.ts helpers.
171
145
  *
172
- * @returns Promise that resolves when reconnection succeeds or logs on failure
146
+ * @returns Context adapter for reconnect/retry helpers
173
147
  */
174
- private _doReconnect;
148
+ private _reconnectOps;
175
149
  /**
176
- * Log a retry warning and, if disconnected, attempt to reconnect before waiting.
150
+ * Attempt to reconnect to the MCP server.
151
+ * Deduplicates concurrent calls — only one reconnect runs at a time.
177
152
  *
178
- * @param lastError - The error from the failed attempt
179
- * @param attempt - Zero-based current attempt index
180
- * @param retries - Total retry count
181
- * @returns Promise that resolves after logging, optional reconnect, and inter-retry delay
153
+ * @returns Resolves when reconnect succeeds or all attempts are exhausted
182
154
  */
183
- private _handleRetryAttempt;
155
+ reconnect(): Promise<void>;
184
156
  /**
185
157
  * Call an MCP tool with automatic retry on timeout or connection loss.
186
158
  * Reconnects automatically if the connection was lost between attempts.
@@ -1,45 +1,9 @@
1
1
  // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * @module MCP/transport/connection
5
- * @description MCPConnection — JSON-RPC 2.0 transport over stdio or HTTP gateway.
6
- * Supports two transport modes:
7
- * - **stdio**: Spawns the EP MCP server binary as a child process (default)
8
- * - **gateway**: Connects to an MCP Gateway via HTTP (for agentic workflow environments)
9
- *
10
- * Gateway mode is activated when `EP_MCP_GATEWAY_URL` env var is set or
11
- * `gatewayUrl` is provided in options.
12
- */
13
- import { spawn } from 'child_process';
14
- import { resolve, dirname } from 'path';
15
- import { fileURLToPath } from 'url';
16
3
  import { MCPRateLimitError, MCPSessionExpiredError } from './errors.js';
17
- import { isRetriableError, RECONNECT_MAX_DELAY_MS } from './retry-policy.js';
18
4
  import { attemptGatewayConnection, sendGatewayRequest } from './gateway.js';
19
- /** npm binary name for the European Parliament MCP server */
20
- const BINARY_NAME = 'european-parliament-mcp-server';
21
- /** Platform-specific binary filename (Windows uses .cmd shim) */
22
- const BINARY_FILE = process.platform === 'win32' ? `${BINARY_NAME}.cmd` : BINARY_NAME;
23
- /** Default binary resolved from node_modules/.bin relative to this file's compiled location */
24
- const DEFAULT_SERVER_BINARY = resolve(dirname(fileURLToPath(import.meta.url)), `../../../node_modules/.bin/${BINARY_FILE}`);
25
- /** Default request timeout in milliseconds — EU Parliament API responses commonly take 30-120+ seconds for large datasets */
26
- const DEFAULT_REQUEST_TIMEOUT_MS = 180_000;
27
- /**
28
- * Effective request timeout, configurable via `EP_REQUEST_TIMEOUT_MS` env var.
29
- * This keeps the client-side timeout aligned with the MCP server timeout set
30
- * in workflow configs and copilot-mcp.json.
31
- */
32
- const REQUEST_TIMEOUT_MS = (() => {
33
- const envVal = process.env['EP_REQUEST_TIMEOUT_MS'];
34
- if (envVal) {
35
- const parsed = Number(envVal);
36
- if (!Number.isNaN(parsed) && parsed > 0)
37
- return parsed;
38
- }
39
- return DEFAULT_REQUEST_TIMEOUT_MS;
40
- })();
41
- /** Connection startup delay in milliseconds */
42
- const CONNECTION_STARTUP_DELAY_MS = 500;
5
+ import { REQUEST_TIMEOUT_MS, DEFAULT_SERVER_BINARY, attemptStdioConnection, handleIncomingMessage, } from './process.js';
6
+ import { performReconnect, runWithRetry } from './reconnect.js';
43
7
  /**
44
8
  * Base MCP connection managing JSON-RPC 2.0 transport over stdio or HTTP gateway.
45
9
  * Extended by domain-specific clients to add tool wrapper methods.
@@ -148,40 +112,6 @@ export class MCPConnection {
148
112
  connected: this.connected,
149
113
  };
150
114
  }
151
- /**
152
- * Compute the delay before the next connection attempt.
153
- * Respects `Retry-After` carried by {@link MCPRateLimitError}; otherwise uses
154
- * exponential back-off (`connectionRetryDelay * 2^(attempt - 1)`).
155
- *
156
- * @param error - The error from the failed attempt
157
- * @param attempt - Number of attempts made so far (1-indexed)
158
- * @returns Delay in milliseconds
159
- */
160
- _computeConnectionDelay(error, attempt) {
161
- if (error instanceof MCPRateLimitError && error.retryAfterMs > 0) {
162
- return error.retryAfterMs;
163
- }
164
- return this.connectionRetryDelay * Math.pow(2, attempt - 1);
165
- }
166
- /**
167
- * Handle a single connection attempt error: re-throw immediately for non-retriable errors
168
- * (e.g. session expiry), increment the attempt counter, and return the delay to wait
169
- * before the next attempt. Throws when the maximum attempts have been exhausted.
170
- *
171
- * @param error - The error from the failed attempt
172
- * @returns Delay in milliseconds to wait before the next attempt
173
- */
174
- _handleConnectionAttemptError(error) {
175
- if (error instanceof MCPSessionExpiredError) {
176
- throw error;
177
- }
178
- this.connectionAttempts++;
179
- if (this.connectionAttempts >= this.maxConnectionAttempts) {
180
- console.error('❌ Failed to connect to MCP server after', this.maxConnectionAttempts, 'attempts');
181
- throw error;
182
- }
183
- return this._computeConnectionDelay(error, this.connectionAttempts);
184
- }
185
115
  /**
186
116
  * Connect to the MCP server with retry logic
187
117
  */
@@ -200,7 +130,7 @@ export class MCPConnection {
200
130
  while (this.connectionAttempts < this.maxConnectionAttempts) {
201
131
  try {
202
132
  if (this.gatewayUrl) {
203
- await this._attemptGatewayConnection();
133
+ await attemptGatewayConnection(this._gatewayContext());
204
134
  }
205
135
  else {
206
136
  await this._attemptConnection();
@@ -209,18 +139,36 @@ export class MCPConnection {
209
139
  return;
210
140
  }
211
141
  catch (error) {
212
- const delay = this._handleConnectionAttemptError(error);
142
+ const delay = this._nextConnectionDelay(error);
213
143
  console.warn(`⚠️ Connection attempt ${this.connectionAttempts} failed. Retrying in ${delay}ms...`);
214
144
  await new Promise((resolve) => setTimeout(resolve, delay));
215
145
  }
216
146
  }
217
147
  }
218
148
  /**
219
- * Build a {@link GatewayContext} adapter for delegating to gateway helpers.
149
+ * Advance the attempt counter and compute the next retry delay.
150
+ * Throws immediately for non-retriable errors or when exhausted.
220
151
  *
221
- * @returns A context adapter exposing the connection-level fields the
222
- * gateway helpers need (URL, API key, session ID accessors, request-ID
223
- * counter, connection-state setter).
152
+ * @param error - The error from the failed attempt
153
+ * @returns Delay in milliseconds to wait before the next attempt
154
+ */
155
+ _nextConnectionDelay(error) {
156
+ if (error instanceof MCPSessionExpiredError)
157
+ throw error;
158
+ this.connectionAttempts++;
159
+ if (this.connectionAttempts >= this.maxConnectionAttempts) {
160
+ console.error('❌ Failed to connect to MCP server after', this.maxConnectionAttempts, 'attempts');
161
+ throw error;
162
+ }
163
+ if (error instanceof MCPRateLimitError && error.retryAfterMs > 0) {
164
+ return error.retryAfterMs;
165
+ }
166
+ return this.connectionRetryDelay * Math.pow(2, this.connectionAttempts - 1);
167
+ }
168
+ /**
169
+ * Build a {@link GatewayContext} adapter for gateway.ts helpers.
170
+ *
171
+ * @returns Context adapter for gateway.ts helpers
224
172
  */
225
173
  _gatewayContext() {
226
174
  return {
@@ -237,72 +185,39 @@ export class MCPConnection {
237
185
  },
238
186
  };
239
187
  }
240
- /** Attempt a single connection via MCP Gateway (HTTP transport). */
241
- async _attemptGatewayConnection() {
242
- await attemptGatewayConnection(this._gatewayContext());
243
- }
244
188
  /**
245
- * Attempt a single connection via stdio (spawns server binary)
189
+ * Build a {@link SpawnContext} adapter for process.ts spawn helpers.
190
+ *
191
+ * @returns Context adapter for stdio spawn helpers
246
192
  */
247
- async _attemptConnection() {
248
- try {
249
- const isJavaScriptFile = this.serverPath.toLowerCase().endsWith('.js');
250
- const command = isJavaScriptFile ? process.execPath : this.serverPath;
251
- const args = isJavaScriptFile ? [this.serverPath] : [];
252
- const childEnv = { ...process.env };
253
- const effectiveTimeoutMs = childEnv['EP_REQUEST_TIMEOUT_MS']
254
- ? Number(childEnv['EP_REQUEST_TIMEOUT_MS'])
255
- : REQUEST_TIMEOUT_MS;
256
- childEnv['EP_REQUEST_TIMEOUT_MS'] = String(effectiveTimeoutMs);
257
- if (!isJavaScriptFile) {
258
- args.push('--timeout', String(effectiveTimeoutMs));
259
- }
260
- this.process = spawn(command, args, {
261
- stdio: ['pipe', 'pipe', 'pipe'],
262
- env: childEnv,
263
- });
264
- let buffer = '';
265
- let startupError = null;
266
- this.process.stdout?.on('data', (data) => {
267
- buffer += data.toString();
268
- const lines = buffer.split('\n');
269
- buffer = lines.pop() ?? '';
270
- for (const line of lines) {
271
- if (line.trim()) {
272
- this.handleMessage(line);
273
- }
274
- }
275
- });
276
- this.process.stderr?.on('data', (data) => {
277
- const message = data.toString().trim();
278
- if (message) {
279
- console.error(`MCP Server: ${message}`);
280
- }
281
- });
282
- this.process.on('close', (code) => {
283
- console.log(`MCP Server exited with code ${code}`);
284
- this.connected = false;
193
+ _spawnContext() {
194
+ return {
195
+ serverPath: this.serverPath,
196
+ serverLabel: this.serverLabel,
197
+ requestTimeoutMs: REQUEST_TIMEOUT_MS,
198
+ setProcess: (p) => {
199
+ this.process = p;
200
+ },
201
+ setConnected: (v) => {
202
+ this.connected = v;
203
+ },
204
+ onMessage: (line) => this.handleMessage(line),
205
+ rejectAllPending: (msg) => {
285
206
  for (const [id, { reject }] of this.pendingRequests.entries()) {
286
- reject(new Error('MCP server connection closed'));
207
+ reject(new Error(msg));
287
208
  this.pendingRequests.delete(id);
288
209
  }
289
- });
290
- this.process.on('error', (err) => {
291
- startupError = err;
292
- this.connected = false;
293
- });
294
- await new Promise((resolve) => setTimeout(resolve, CONNECTION_STARTUP_DELAY_MS));
295
- if (startupError) {
296
- throw startupError;
297
- }
298
- this.connected = true;
299
- console.log(`✅ Connected to ${this.serverLabel}`);
300
- }
301
- catch (error) {
302
- const message = error instanceof Error ? error.message : String(error);
303
- console.error('❌ Failed to spawn MCP server:', message);
304
- throw error;
305
- }
210
+ },
211
+ };
212
+ }
213
+ /**
214
+ * Attempt a single connection via stdio (spawns server binary).
215
+ * Delegates to {@link attemptStdioConnection} in process.ts.
216
+ *
217
+ * @returns Resolves when the spawn has completed (or rejects on spawn failure)
218
+ */
219
+ async _attemptConnection() {
220
+ return attemptStdioConnection(this._spawnContext());
306
221
  }
307
222
  /**
308
223
  * Disconnect from the MCP server
@@ -316,49 +231,13 @@ export class MCPConnection {
316
231
  this.mcpSessionId = null;
317
232
  }
318
233
  /**
319
- * Handle incoming messages from MCP server (stdio mode only)
234
+ * Handle incoming messages from MCP server (stdio mode only).
235
+ * Delegates to {@link handleIncomingMessage} in process.ts.
320
236
  *
321
237
  * @param line - JSON message line from server
322
238
  */
323
239
  handleMessage(line) {
324
- try {
325
- const message = JSON.parse(line);
326
- if (message.id !== null && message.id !== undefined && this.pendingRequests.has(message.id)) {
327
- const pending = this.pendingRequests.get(message.id);
328
- if (pending) {
329
- this.pendingRequests.delete(message.id);
330
- if (message.error) {
331
- pending.reject(new Error(message.error.message ?? 'MCP server error'));
332
- }
333
- else {
334
- pending.resolve(message.result);
335
- }
336
- }
337
- else {
338
- this.pendingRequests.delete(message.id);
339
- console.error(`MCP pending request ${String(message.id)} vanished before handling`);
340
- }
341
- }
342
- else if ((message.id === null || message.id === undefined) && message.method) {
343
- console.log(`MCP Notification: ${message.method}`);
344
- }
345
- }
346
- catch (error) {
347
- const errorMessage = error instanceof Error ? error.message : String(error);
348
- console.error('Error parsing MCP message:', errorMessage);
349
- console.error('Problematic line:', line);
350
- }
351
- }
352
- /**
353
- * Send a request via MCP Gateway (HTTP transport). Delegates to the
354
- * gateway helper module.
355
- *
356
- * @param method - RPC method name
357
- * @param params - Method parameters
358
- * @returns Server response result payload
359
- */
360
- async _sendGatewayRequest(method, params = {}) {
361
- return sendGatewayRequest(method, params, this._gatewayContext());
240
+ handleIncomingMessage(line, this.pendingRequests);
362
241
  }
363
242
  /**
364
243
  * Send a request to the MCP server
@@ -372,7 +251,7 @@ export class MCPConnection {
372
251
  throw new Error('Not connected to MCP server');
373
252
  }
374
253
  if (this.gatewayUrl) {
375
- return (await this._sendGatewayRequest(method, params));
254
+ return (await sendGatewayRequest(method, params, this._gatewayContext()));
376
255
  }
377
256
  const id = ++this.requestId;
378
257
  const request = {
@@ -418,68 +297,43 @@ export class MCPConnection {
418
297
  return this.sendRequest('tools/call', { name, arguments: args });
419
298
  }
420
299
  /**
421
- * Attempt to reconnect to the MCP server with exponential back-off.
422
- * Concurrent callers await the same in-flight reconnect instead of no-oping,
423
- * ensuring the connection is re-established before all waiting callers continue.
424
- *
425
- * @returns Promise that resolves when reconnection succeeds or all attempts are exhausted
426
- */
427
- async reconnect() {
428
- if (this.reconnectingPromise !== null) {
429
- return this.reconnectingPromise;
430
- }
431
- this.reconnectCount++;
432
- console.log(`🔄 Reconnecting to ${this.serverLabel} (attempt ${this.reconnectCount})...`);
433
- this.reconnectingPromise = this._doReconnect();
434
- try {
435
- await this.reconnectingPromise;
436
- }
437
- finally {
438
- this.reconnectingPromise = null;
439
- }
440
- }
441
- /**
442
- * Internal reconnect helper.
443
- *
444
- * Waits for an exponential back-off delay derived from the current
445
- * `reconnectCount`, then delegates to `connect()` which handles its own
446
- * retry loop. This avoids composing N×N attempts.
300
+ * Build a {@link ReconnectOps} adapter for reconnect.ts helpers.
447
301
  *
448
- * @returns Promise that resolves when reconnection succeeds or logs on failure
302
+ * @returns Context adapter for reconnect/retry helpers
449
303
  */
450
- async _doReconnect() {
451
- const normalizedMaxAttempts = Math.max(1, this.maxConnectionAttempts);
452
- const attemptIndex = Math.min(Math.max(0, this.reconnectCount - 1), normalizedMaxAttempts - 1);
453
- const delay = Math.min(this.connectionRetryDelay * Math.pow(2, attemptIndex), RECONNECT_MAX_DELAY_MS);
454
- await new Promise((r) => setTimeout(r, delay));
455
- try {
456
- this.connected = false;
457
- await this.connect();
458
- }
459
- catch (error) {
460
- console.error(`❌ Reconnection to ${this.serverLabel} failed: ${error instanceof Error ? error.message : String(error)}`);
461
- }
304
+ _reconnectOps() {
305
+ return {
306
+ maxConnectionAttempts: this.maxConnectionAttempts,
307
+ connectionRetryDelay: this.connectionRetryDelay,
308
+ serverLabel: this.serverLabel,
309
+ callTool: (n, a) => this.callTool(n, a),
310
+ isConnected: () => this.connected,
311
+ connect: () => this.connect(),
312
+ setConnected: (v) => {
313
+ this.connected = v;
314
+ },
315
+ getReconnectCount: () => this.reconnectCount,
316
+ setReconnectCount: (n) => {
317
+ this.reconnectCount = n;
318
+ },
319
+ getReconnectingPromise: () => this.reconnectingPromise,
320
+ setReconnectingPromise: (p) => {
321
+ this.reconnectingPromise = p;
322
+ },
323
+ getTimeoutCount: () => this.timeoutCount,
324
+ setTimeoutCount: (n) => {
325
+ this.timeoutCount = n;
326
+ },
327
+ };
462
328
  }
463
329
  /**
464
- * Log a retry warning and, if disconnected, attempt to reconnect before waiting.
330
+ * Attempt to reconnect to the MCP server.
331
+ * Deduplicates concurrent calls — only one reconnect runs at a time.
465
332
  *
466
- * @param lastError - The error from the failed attempt
467
- * @param attempt - Zero-based current attempt index
468
- * @param retries - Total retry count
469
- * @returns Promise that resolves after logging, optional reconnect, and inter-retry delay
333
+ * @returns Resolves when reconnect succeeds or all attempts are exhausted
470
334
  */
471
- async _handleRetryAttempt(lastError, attempt, retries) {
472
- if (lastError.message.toLowerCase().includes('timeout')) {
473
- this.timeoutCount++;
474
- console.warn(`⏱️ Request timeout (total: ${this.timeoutCount}), retrying ${attempt + 1}/${retries}...`);
475
- }
476
- else {
477
- console.warn(`⚠️ Request failed, retrying ${attempt + 1}/${retries}: ${lastError.message}`);
478
- }
479
- if (!this.connected) {
480
- await this.reconnect();
481
- }
482
- await new Promise((r) => setTimeout(r, this.connectionRetryDelay * (attempt + 1)));
335
+ async reconnect() {
336
+ return performReconnect(this._reconnectOps());
483
337
  }
484
338
  /**
485
339
  * Call an MCP tool with automatic retry on timeout or connection loss.
@@ -499,21 +353,7 @@ export class MCPConnection {
499
353
  if (retries < 0) {
500
354
  throw new RangeError(`maxRetries must be >= 0, received ${retries}`);
501
355
  }
502
- let lastError = new Error(`Failed to call tool '${name}' after ${retries} retries`);
503
- for (let attempt = 0; attempt <= retries; attempt++) {
504
- try {
505
- return await this.callTool(name, args);
506
- }
507
- catch (error) {
508
- lastError = error instanceof Error ? error : new Error(String(error));
509
- if (!isRetriableError(lastError))
510
- throw lastError;
511
- if (attempt === retries)
512
- break;
513
- await this._handleRetryAttempt(lastError, attempt, retries);
514
- }
515
- }
516
- throw lastError;
356
+ return runWithRetry(name, args, retries, this._reconnectOps());
517
357
  }
518
358
  }
519
359
  //# sourceMappingURL=connection.js.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @module MCP/transport/process
3
+ * @description Stdio process spawn / teardown helpers and JSON-RPC message routing
4
+ * for the MCPConnection stdio transport.
5
+ *
6
+ * Extracted from `connection.ts` to keep individual file sizes under 400 LOC.
7
+ * Operates on an explicit {@link SpawnContext} adapter rather than `this`.
8
+ */
9
+ import { type ChildProcess } from 'child_process';
10
+ import type { PendingRequest } from '../../types/index.js';
11
+ /** npm binary name for the European Parliament MCP server */
12
+ export declare const BINARY_NAME = "european-parliament-mcp-server";
13
+ /** Platform-specific binary filename (Windows uses .cmd shim) */
14
+ export declare const BINARY_FILE: string;
15
+ /** Default binary resolved from node_modules/.bin relative to this file's compiled location */
16
+ export declare const DEFAULT_SERVER_BINARY: string;
17
+ /** Default request timeout in milliseconds — EU Parliament API responses commonly take 30-120+ seconds for large datasets */
18
+ export declare const DEFAULT_REQUEST_TIMEOUT_MS = 180000;
19
+ /**
20
+ * Effective request timeout, configurable via `EP_REQUEST_TIMEOUT_MS` env var.
21
+ * This keeps the client-side timeout aligned with the MCP server timeout set
22
+ * in workflow configs and copilot-mcp.json.
23
+ */
24
+ export declare const REQUEST_TIMEOUT_MS: number;
25
+ /** Connection startup delay in milliseconds */
26
+ export declare const CONNECTION_STARTUP_DELAY_MS = 500;
27
+ /**
28
+ * Adapter passed by {@link MCPConnection} to stdio spawn helpers.
29
+ * Lets the helpers read the few connection-level fields they need and
30
+ * mutate process state without pulling in the whole connection class.
31
+ */
32
+ export interface SpawnContext {
33
+ /** Resolved path to the server binary or JS entry-point */
34
+ readonly serverPath: string;
35
+ /** Human-readable label for log messages */
36
+ readonly serverLabel: string;
37
+ /** Per-request timeout (milliseconds) */
38
+ readonly requestTimeoutMs: number;
39
+ /** Store or clear the spawned child process */
40
+ readonly setProcess: (p: ChildProcess | null) => void;
41
+ /** Mark the connection as (dis)connected */
42
+ readonly setConnected: (v: boolean) => void;
43
+ /** Route an incoming newline-delimited JSON-RPC message line */
44
+ readonly onMessage: (line: string) => void;
45
+ /** Reject and clear all in-flight pending requests with an error */
46
+ readonly rejectAllPending: (message: string) => void;
47
+ }
48
+ /**
49
+ * Attempt a single connection via stdio (spawns server binary).
50
+ *
51
+ * @param ctx - Spawn context adapter from MCPConnection
52
+ */
53
+ export declare function attemptStdioConnection(ctx: SpawnContext): Promise<void>;
54
+ /**
55
+ * Handle an incoming newline-delimited JSON-RPC message from the server.
56
+ * Routes responses to matching in-flight pending requests; logs notifications.
57
+ *
58
+ * @param line - Single JSON-RPC message line (no trailing newline)
59
+ * @param pending - In-flight request map maintained by {@link MCPConnection}
60
+ */
61
+ export declare function handleIncomingMessage(line: string, pending: Map<number, PendingRequest>): void;
62
+ //# sourceMappingURL=process.d.ts.map