33/**
44 * MCP Client for riksdag-regering-mcp Server
55 *
6- * HTTP client for accessing Swedish Parliament and Government data
6+ * JSON-RPC 2.0 client for accessing Swedish Parliament and Government data
77 * via the riksdag-regering-mcp server (32 specialized tools).
88 *
99 * Server: https://riksdag-regering-ai.onrender.com/mcp
10+ * Protocol: JSON-RPC 2.0 (https://www.jsonrpc.org/specification)
1011 * Tools: 32 tools for Swedish political data (Riksdag, Regering, MPs, votes, documents)
1112 *
13+ * MCP Protocol:
14+ * - POST to /mcp endpoint (not /mcp/tools/{tool})
15+ * - JSON-RPC 2.0 format with method: 'tools/call'
16+ * - Tool names: 'riksdag-regering--{tool}' (e.g., 'riksdag-regering--get_calendar_events')
17+ *
1218 * Usage:
1319 * import { MCPClient } from './mcp-client.js';
1420 * const client = new MCPClient();
1521 * const events = await client.fetchCalendarEvents('2026-02-10', '2026-02-17');
22+ *
23+ * Error Handling:
24+ * - Automatic retries on network errors (max 3 attempts with exponential backoff)
25+ * - Fallback from prefixed to non-prefixed tool names
26+ * - Detailed error messages with troubleshooting hints
1627 */
1728
1829const DEFAULT_MCP_SERVER_URL = process . env . MCP_SERVER_URL || 'https://riksdag-regering-ai.onrender.com/mcp' ;
1930const DEFAULT_REQUEST_TIMEOUT = 30000 ; // 30 seconds
2031const DEFAULT_MAX_RETRIES = 3 ;
2132const RETRY_DELAY = 1000 ; // 1 second
2233
34+ // JSON-RPC 2.0 request ID counter
35+ let jsonRpcId = 1 ;
36+
2337/**
2438 * MCP Client Class
2539 */
@@ -41,7 +55,7 @@ export class MCPClient {
4155 }
4256
4357 /**
44- * Make HTTP request to MCP server
58+ * Make HTTP request to MCP server using JSON-RPC 2.0 protocol
4559 *
4660 * @param {string } tool - Tool name (e.g., 'get_calendar_events')
4761 * @param {Object } params - Tool parameters
@@ -59,27 +73,65 @@ export class MCPClient {
5973 this . requestCount ++ ;
6074 }
6175
76+ const controller = new AbortController ( ) ;
77+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , this . timeout ) ;
78+
6279 try {
63- const controller = new AbortController ( ) ;
64- const timeoutId = setTimeout ( ( ) => controller . abort ( ) , this . timeout ) ;
80+ // MCP uses JSON-RPC 2.0 protocol
81+ // Call the tool using tools/call method
82+ // Try with server prefix first (riksdag-regering--tool_name)
83+ // If that fails with "tool not found", try without prefix
84+ const toolName = tool . includes ( '--' ) ? tool : `riksdag-regering--${ tool } ` ;
85+
86+ const jsonRpcRequest = {
87+ jsonrpc : '2.0' ,
88+ id : jsonRpcId ++ ,
89+ method : 'tools/call' ,
90+ params : {
91+ name : toolName ,
92+ arguments : params
93+ }
94+ } ;
6595
66- const response = await fetch ( ` ${ this . baseURL } /tools/ ${ tool } ` , {
96+ const response = await fetch ( this . baseURL , {
6797 method : 'POST' ,
6898 headers : {
6999 'Content-Type' : 'application/json' ,
70100 } ,
71- body : JSON . stringify ( params ) ,
101+ body : JSON . stringify ( jsonRpcRequest ) ,
72102 signal : controller . signal
73103 } ) ;
74104
75- clearTimeout ( timeoutId ) ;
76-
77105 if ( ! response . ok ) {
78- throw new Error ( `MCP server error: ${ response . status } ${ response . statusText } ` ) ;
106+ // Provide more detailed error information
107+ let errorBody = '' ;
108+ try {
109+ errorBody = await response . text ( ) ;
110+ } catch ( e ) {
111+ // Ignore if we can't read the body
112+ }
113+ throw new Error ( `MCP server error: ${ response . status } ${ response . statusText } ${ errorBody ? ' - ' + errorBody : '' } ` ) ;
79114 }
80115
81- const data = await response . json ( ) ;
82- return data ;
116+ const jsonRpcResponse = await response . json ( ) ;
117+
118+ // Check for JSON-RPC error
119+ if ( jsonRpcResponse . error ) {
120+ const errorMsg = jsonRpcResponse . error . message || JSON . stringify ( jsonRpcResponse . error ) ;
121+
122+ // If tool not found and we used prefix, try without prefix
123+ // Guard against infinite recursion by checking if tool already doesn't have prefix
124+ if ( errorMsg . includes ( 'not found' ) && toolName . startsWith ( 'riksdag-regering--' ) ) {
125+ console . warn ( `⚠️ Tool not found with prefix, retrying without prefix...` ) ;
126+ // Recursive call without prefix
127+ return this . request ( tool . replace ( / ^ r i k s d a g - r e g e r i n g - - / , '' ) , params , retryCount ) ;
128+ }
129+
130+ throw new Error ( `MCP tool error: ${ errorMsg } ` ) ;
131+ }
132+
133+ // Extract result from JSON-RPC response
134+ return jsonRpcResponse . result || { } ;
83135
84136 } catch ( error ) {
85137 // Retry on network errors (case-insensitive check)
@@ -99,6 +151,9 @@ export class MCPClient {
99151 this . errorCount ++ ;
100152
101153 throw new Error ( `MCP request failed: ${ error . message } ` ) ;
154+ } finally {
155+ // Always clear timeout to prevent timer leak
156+ clearTimeout ( timeoutId ) ;
102157 }
103158 }
104159
0 commit comments