From 96a99e97c9468f7594fd2de4324b006d75b07948 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:20:55 +0000 Subject: [PATCH 1/8] fix(geospatial): Align parameter with server expectation A geospatial request was sending a `query` parameter, but the server expected `searchText`. This caused a validation error. This commit renames the parameter to `searchText` to match the server's API, resolving the error. --- lib/agents/tools/geospatial.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 80a05738..826ba48f 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -202,9 +202,9 @@ export const geospatialTool = ({ uiStream.update(); const toolName = queryType === 'directions' ? 'mapbox_directions' : 'mapbox_geocoding'; - const toolArgs = { - query, - includeMapPreview: includeMap !== false + const toolArgs = { + searchText: query, + includeMapPreview: includeMap !== false, }; console.log('[GeospatialTool] Calling tool:', toolName, 'with args:', toolArgs); From 26bf23e4518592977b72a69ee76bfede131cd233 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:36:28 +0000 Subject: [PATCH 2/8] fix(geospatial): Correct server URL and align tool parameter This commit addresses two issues: 1. **Parameter Mismatch:** The geospatial tool was sending a `query` parameter, but the server expected `searchText`. This has been corrected. 2. **Incorrect Server URL:** The tool was constructing an incorrect URL, leading to a 404 error. The `mcpServerBaseUrl` has been updated to the correct endpoint you provided, which includes the necessary user scope. --- lib/agents/tools/geospatial.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 826ba48f..0ece89f7 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -50,7 +50,7 @@ async function getConnectedMcpClient(): Promise { } const smitheryUrlOptions = { config, apiKey, profileId }; - const mcpServerBaseUrl = `https://server.smithery.ai/mapbox-mcp-server/mcp?api_key=${smitheryUrlOptions.apiKey}&profile=${smitheryUrlOptions.profileId}`; + const mcpServerBaseUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server`; let serverUrlToUse; try { From 7755b0bcb46b255349943b97287a7db7807227d5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:45:16 +0000 Subject: [PATCH 3/8] I've made a commit to fix the geospatial tool. Here's a summary of the changes: 1. **Authentication:** I've updated the HTTP transport to send the API key as a Bearer token in the `Authorization` header, which resolves the 401 Unauthorized error. 2. **Server URL:** I've corrected the base URL for the Smithery server to point to your user-specific endpoint, fixing the 404 Not Found error. 3. **Tool Parameter:** I've changed the `mapbox_geocoding` tool call to use the `searchText` parameter instead of `query`. This aligns with the server's API and fixes the initial tool execution failure. --- lib/agents/tools/geospatial.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 0ece89f7..ae940dfc 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -76,7 +76,11 @@ async function getConnectedMcpClient(): Promise { let client; try { - transport = new StreamableHTTPClientTransport(serverUrlToUse); + transport = new StreamableHTTPClientTransport(serverUrlToUse, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); console.log('[GeospatialTool] Transport created successfully'); } catch (transportError: any) { console.error('[GeospatialTool] Failed to create transport:', transportError.message); From 091e0041c1b5823b6efbe5d03860589476e3e52a Mon Sep 17 00:00:00 2001 From: EreQ Date: Thu, 14 Aug 2025 13:50:52 +0300 Subject: [PATCH 4/8] Update geospatial.tsx --- lib/agents/tools/geospatial.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index ae940dfc..1544884e 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -50,7 +50,7 @@ async function getConnectedMcpClient(): Promise { } const smitheryUrlOptions = { config, apiKey, profileId }; - const mcpServerBaseUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server`; + const mcpServerBaseUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server?api_key=${smitheryUrlOptions.apiKey}&profile=${smitheryUrlOptions.profileId}`; let serverUrlToUse; try { @@ -75,12 +75,9 @@ async function getConnectedMcpClient(): Promise { let transport; let client; - try { - transport = new StreamableHTTPClientTransport(serverUrlToUse, { - headers: { - Authorization: `Bearer ${apiKey}`, - }, - }); + + transport = new StreamableHTTPClientTransport(serverUrlToUse); + console.log('[GeospatialTool] Transport created successfully'); } catch (transportError: any) { console.error('[GeospatialTool] Failed to create transport:', transportError.message); From d9d12b10a44125e87e6ec4a0803eaad3684cd1cd Mon Sep 17 00:00:00 2001 From: EreQ Date: Thu, 14 Aug 2025 14:34:42 +0300 Subject: [PATCH 5/8] Update geospatial.tsx --- lib/agents/tools/geospatial.tsx | 118 ++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 1544884e..6f338022 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -15,14 +15,20 @@ async function getConnectedMcpClient(): Promise { const mapboxAccessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN; const profileId = process.env.NEXT_PUBLIC_SMITHERY_PROFILE_ID; + // Log environment variables (masked for security) console.log('[GeospatialTool] Environment check:', { apiKey: apiKey ? `${apiKey.substring(0, 8)}...` : 'MISSING', mapboxAccessToken: mapboxAccessToken ? `${mapboxAccessToken.substring(0, 8)}...` : 'MISSING', profileId: profileId ? `${profileId.substring(0, 8)}...` : 'MISSING', }); + // Validate environment variables if (!apiKey || !mapboxAccessToken || !profileId) { - console.error('[GeospatialTool] Missing required environment variables'); + console.error('[GeospatialTool] Missing required environment variables:', { + apiKey: !!apiKey, + mapboxAccessToken: !!mapboxAccessToken, + profileId: !!profileId, + }); return null; } @@ -31,12 +37,20 @@ async function getConnectedMcpClient(): Promise { return null; } + // Validate profile ID format (UUID) + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidRegex.test(profileId)) { + console.error('[GeospatialTool] Invalid profile ID format:', profileId); + return null; + } + + // Load configuration let config; try { const mapboxMcpConfig = await import('QCX/mapbox_mcp_config.json'); - config = { - ...mapboxMcpConfig.default || mapboxMcpConfig, - mapboxAccessToken + config = { + ...mapboxMcpConfig.default || mapboxMcpConfig, + mapboxAccessToken, }; console.log('[GeospatialTool] Config loaded successfully'); } catch (configError: any) { @@ -44,50 +58,61 @@ async function getConnectedMcpClient(): Promise { config = { mapboxAccessToken, version: '1.0.0', - name: 'mapbox-mcp-server' + name: 'mapbox-mcp-server', }; console.log('[GeospatialTool] Using fallback config'); } - const smitheryUrlOptions = { config, apiKey, profileId }; - const mcpServerBaseUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server?api_key=${smitheryUrlOptions.apiKey}&profile=${smitheryUrlOptions.profileId}`; + // Create Smithery URL without API key in query parameters + const mcpServerBaseUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server?profile=${profileId}`; + const smitheryUrlOptions = { config, profileId }; + let serverUrlToUse: URL; - let serverUrlToUse; try { serverUrlToUse = createSmitheryUrl(mcpServerBaseUrl, smitheryUrlOptions); const urlDisplay = serverUrlToUse.toString().split('?')[0]; console.log('[GeospatialTool] MCP Server URL created:', urlDisplay); - + + // Validate URL if (!serverUrlToUse.href || !serverUrlToUse.href.startsWith('https://')) { - throw new Error('Invalid server URL generated'); + throw new Error('Invalid server URL: Must use HTTPS protocol'); } } catch (urlError: any) { console.error('[GeospatialTool] Error creating Smithery URL:', urlError.message); - console.error('[GeospatialTool] URL options:', { + console.error('[GeospatialTool] URL options:', { baseUrl: mcpServerBaseUrl, hasConfig: !!config, - hasApiKey: !!apiKey, - hasProfileId: !!profileId + hasProfileId: !!profileId, }); return null; } + // Initialize transport with authentication headers let transport; let client; - - - transport = new StreamableHTTPClientTransport(serverUrlToUse); + const headers = { + Authorization: `Bearer ${apiKey}`, + 'X-Profile-ID': profileId, + 'Content-Type': 'application/json', + }; + try { + console.log('[GeospatialTool] Initializing transport with headers:', { + Authorization: `Bearer ${apiKey.substring(0, 8)}...`, + 'X-Profile-ID': profileId.substring(0, 8) + '...', + }); + transport = new StreamableHTTPClientTransport(serverUrlToUse, { headers }); console.log('[GeospatialTool] Transport created successfully'); } catch (transportError: any) { console.error('[GeospatialTool] Failed to create transport:', transportError.message); return null; } + // Initialize MCP client try { - client = new MCPClientClass({ - name: 'GeospatialToolClient', - version: '1.0.0' + client = new MCPClientClass({ + name: 'GeospatialToolClient', + version: '1.0.0', }); console.log('[GeospatialTool] MCP Client instance created'); } catch (clientError: any) { @@ -95,25 +120,27 @@ async function getConnectedMcpClient(): Promise { return null; } + // Connect to MCP server with timeout try { console.log('[GeospatialTool] Attempting to connect to MCP server...'); - + await Promise.race([ client.connect(transport), new Promise((_, reject) => { - setTimeout(() => reject(new Error('Connection timeout after 15 seconds')), 15000); + setTimeout(() => reject(new Error('Connection timeout after 30 seconds')), 30000); }), ]); - + console.log('[GeospatialTool] Successfully connected to MCP server'); - + + // List available tools for debugging try { const tools = await client.listTools(); console.log('[GeospatialTool] Available tools:', tools.tools?.map(t => t.name) || []); } catch (listError: any) { console.warn('[GeospatialTool] Could not list tools:', listError.message); } - + return client; } catch (connectionError: any) { console.error('[GeospatialTool] MCP connection failed:', connectionError.message); @@ -121,8 +148,11 @@ async function getConnectedMcpClient(): Promise { name: connectionError.name, stack: connectionError.stack?.split('\n')[0], serverUrl: serverUrlToUse?.toString().split('?')[0], + httpStatus: connectionError.response?.status, + httpResponse: connectionError.response?.data, }); - + + // Ensure client is closed on failure await closeClient(client); return null; } @@ -130,7 +160,7 @@ async function getConnectedMcpClient(): Promise { async function closeClient(client: MCPClientClass | null) { if (!client) return; - + try { await Promise.race([ client.close(), @@ -157,13 +187,13 @@ export const geospatialTool = ({ - Map-related requests - Geographic information lookup`, parameters: geospatialQuerySchema, - execute: async ({ query, queryType, includeMap }: { - query: string; - queryType?: string; - includeMap?: boolean; + execute: async ({ query, queryType, includeMap }: { + query: string; + queryType?: string; + includeMap?: boolean; }) => { console.log('[GeospatialTool] Execute called with:', { query, queryType, includeMap }); - + const uiFeedbackStream = createStreamableValue(); uiStream.append(); @@ -171,7 +201,7 @@ export const geospatialTool = ({ uiFeedbackStream.update(feedbackMessage); const mcpClient = await getConnectedMcpClient(); - + if (!mcpClient) { feedbackMessage = 'Geospatial functionality is currently unavailable. Please check your configuration and try again.'; uiFeedbackStream.update(feedbackMessage); @@ -186,14 +216,14 @@ export const geospatialTool = ({ }; } - let mcpData: { - location: { - latitude?: number; - longitude?: number; - place_name?: string; - address?: string; - }; - mapUrl?: string; + let mcpData: { + location: { + latitude?: number; + longitude?: number; + place_name?: string; + address?: string; + }; + mapUrl?: string; } | null = null; let toolError: string | null = null; @@ -237,20 +267,20 @@ export const geospatialTool = ({ const geocodeResult = geocodeResultUnknown as { tool_results?: Array<{ content?: unknown }> }; const toolResults = Array.isArray(geocodeResult.tool_results) ? geocodeResult.tool_results : []; - + if (toolResults.length === 0 || !toolResults[0]?.content) { throw new Error('No content returned from mapping service'); } let content = toolResults[0].content; - + if (typeof content === 'string') { const jsonRegex = /```(?:json)?\n?([\s\S]*?)\n?```/; const match = content.match(jsonRegex); if (match) { content = match[1].trim(); } - + try { if (typeof content === 'string') { content = JSON.parse(content); @@ -262,7 +292,7 @@ export const geospatialTool = ({ if (typeof content === 'object' && content !== null) { const parsedData = content as any; - + if (parsedData.location) { mcpData = { location: { From d5455317806790e1a85619cd4b466feac8465107 Mon Sep 17 00:00:00 2001 From: EreQ Date: Thu, 14 Aug 2025 14:42:26 +0300 Subject: [PATCH 6/8] Update geospatial.tsx --- lib/agents/tools/geospatial.tsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 6f338022..fbbb4bee 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -63,9 +63,9 @@ async function getConnectedMcpClient(): Promise { console.log('[GeospatialTool] Using fallback config'); } - // Create Smithery URL without API key in query parameters - const mcpServerBaseUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server?profile=${profileId}`; - const smitheryUrlOptions = { config, profileId }; + // Create Smithery URL with API key and profile ID + const mcpServerBaseUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server?api_key=${apiKey}&profile=${profileId}`; + const smitheryUrlOptions = { config, apiKey, profileId }; let serverUrlToUse: URL; try { @@ -82,26 +82,18 @@ async function getConnectedMcpClient(): Promise { console.error('[GeospatialTool] URL options:', { baseUrl: mcpServerBaseUrl, hasConfig: !!config, + hasApiKey: !!apiKey, hasProfileId: !!profileId, }); return null; } - // Initialize transport with authentication headers + // Initialize transport let transport; let client; - const headers = { - Authorization: `Bearer ${apiKey}`, - 'X-Profile-ID': profileId, - 'Content-Type': 'application/json', - }; - try { - console.log('[GeospatialTool] Initializing transport with headers:', { - Authorization: `Bearer ${apiKey.substring(0, 8)}...`, - 'X-Profile-ID': profileId.substring(0, 8) + '...', - }); - transport = new StreamableHTTPClientTransport(serverUrlToUse, { headers }); + console.log('[GeospatialTool] Initializing transport for:', serverUrlToUse.toString()); + transport = new StreamableHTTPClientTransport(serverUrlToUse); console.log('[GeospatialTool] Transport created successfully'); } catch (transportError: any) { console.error('[GeospatialTool] Failed to create transport:', transportError.message); From 36bebf76b1eebada2c6b6840953fa57f71c1a9b6 Mon Sep 17 00:00:00 2001 From: EreQ Date: Thu, 14 Aug 2025 14:47:49 +0300 Subject: [PATCH 7/8] Update geospatial.tsx --- lib/agents/tools/geospatial.tsx | 156 +------------------------------- 1 file changed, 5 insertions(+), 151 deletions(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index fbbb4bee..92dc9f98 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -37,10 +37,10 @@ async function getConnectedMcpClient(): Promise { return null; } - // Validate profile ID format (UUID) - const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - if (!uuidRegex.test(profileId)) { - console.error('[GeospatialTool] Invalid profile ID format:', profileId); + // Validate profile ID format (basic check for non-empty string with allowed characters) + const profileIdRegex = /^[a-zA-Z0-9-_]+$/; + if (!profileIdRegex.test(profileId)) { + console.error('[GeospatialTool] Invalid profile ID format (must contain only letters, numbers, hyphens, or underscores):', profileId); return null; } @@ -180,150 +180,4 @@ export const geospatialTool = ({ - Geographic information lookup`, parameters: geospatialQuerySchema, execute: async ({ query, queryType, includeMap }: { - query: string; - queryType?: string; - includeMap?: boolean; - }) => { - console.log('[GeospatialTool] Execute called with:', { query, queryType, includeMap }); - - const uiFeedbackStream = createStreamableValue(); - uiStream.append(); - - let feedbackMessage = `Processing geospatial query: "${query}". Connecting to mapping service...`; - uiFeedbackStream.update(feedbackMessage); - - const mcpClient = await getConnectedMcpClient(); - - if (!mcpClient) { - feedbackMessage = 'Geospatial functionality is currently unavailable. Please check your configuration and try again.'; - uiFeedbackStream.update(feedbackMessage); - uiFeedbackStream.done(); - uiStream.update(); - return { - type: 'MAP_QUERY_TRIGGER', - originalUserInput: query, - timestamp: new Date().toISOString(), - mcp_response: null, - error: 'MCP client initialization failed - check environment variables and network connectivity', - }; - } - - let mcpData: { - location: { - latitude?: number; - longitude?: number; - place_name?: string; - address?: string; - }; - mapUrl?: string; - } | null = null; - let toolError: string | null = null; - - try { - feedbackMessage = `Connected to mapping service. Processing "${query}"...`; - uiFeedbackStream.update(feedbackMessage); - uiStream.update(); - - const toolName = queryType === 'directions' ? 'mapbox_directions' : 'mapbox_geocoding'; - const toolArgs = { - searchText: query, - includeMapPreview: includeMap !== false, - }; - - console.log('[GeospatialTool] Calling tool:', toolName, 'with args:', toolArgs); - - // Retry logic for tool call - const MAX_RETRIES = 3; - let retryCount = 0; - let geocodeResultUnknown; - while (retryCount < MAX_RETRIES) { - try { - geocodeResultUnknown = await Promise.race([ - mcpClient.callTool({ name: toolName, arguments: toolArgs }), - new Promise((_, reject) => { - setTimeout(() => reject(new Error('Tool call timeout after 30 seconds')), 30000); - }), - ]); - break; - } catch (error: any) { - retryCount++; - if (retryCount === MAX_RETRIES) { - throw error; - } - console.warn(`[GeospatialTool] Retry ${retryCount}/${MAX_RETRIES} after error: ${error.message}`); - await new Promise(resolve => setTimeout(resolve, 1000)); - } - } - - console.log('[GeospatialTool] Raw tool result:', geocodeResultUnknown); - - const geocodeResult = geocodeResultUnknown as { tool_results?: Array<{ content?: unknown }> }; - const toolResults = Array.isArray(geocodeResult.tool_results) ? geocodeResult.tool_results : []; - - if (toolResults.length === 0 || !toolResults[0]?.content) { - throw new Error('No content returned from mapping service'); - } - - let content = toolResults[0].content; - - if (typeof content === 'string') { - const jsonRegex = /```(?:json)?\n?([\s\S]*?)\n?```/; - const match = content.match(jsonRegex); - if (match) { - content = match[1].trim(); - } - - try { - if (typeof content === 'string') { - content = JSON.parse(content); - } - } catch (parseError) { - console.warn('[GeospatialTool] Content is not JSON, using as string:', content); - } - } - - if (typeof content === 'object' && content !== null) { - const parsedData = content as any; - - if (parsedData.location) { - mcpData = { - location: { - latitude: parsedData.location.latitude, - longitude: parsedData.location.longitude, - place_name: parsedData.location.place_name || parsedData.location.name, - address: parsedData.location.address || parsedData.location.formatted_address, - }, - mapUrl: parsedData.mapUrl || parsedData.map_url, - }; - } else { - throw new Error("Response missing required 'location' field"); - } - } else { - throw new Error("Unexpected response format from mapping service"); - } - - feedbackMessage = `Successfully processed location query for: ${mcpData.location.place_name || query}`; - uiFeedbackStream.update(feedbackMessage); - - } catch (error: any) { - console.error('[GeospatialTool] Tool execution failed:', error.message); - console.error('[GeospatialTool] Error stack:', error.stack); - toolError = `Mapping service error: ${error.message}`; - feedbackMessage = toolError; - uiFeedbackStream.update(feedbackMessage); - } finally { - await closeClient(mcpClient); - uiFeedbackStream.done(); - uiStream.update(); - } - - return { - type: 'MAP_QUERY_TRIGGER', - originalUserInput: query, - queryType: queryType || 'geocode', - timestamp: new Date().toISOString(), - mcp_response: mcpData, - error: toolError, - }; - }, -}); + query: From 95c2b4c4279fefe02e068dff5da433e56353a734 Mon Sep 17 00:00:00 2001 From: EreQ Date: Thu, 14 Aug 2025 14:50:17 +0300 Subject: [PATCH 8/8] Update geospatial.tsx --- lib/agents/tools/geospatial.tsx | 155 ++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 8 deletions(-) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 92dc9f98..5e247f7e 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -37,13 +37,6 @@ async function getConnectedMcpClient(): Promise { return null; } - // Validate profile ID format (basic check for non-empty string with allowed characters) - const profileIdRegex = /^[a-zA-Z0-9-_]+$/; - if (!profileIdRegex.test(profileId)) { - console.error('[GeospatialTool] Invalid profile ID format (must contain only letters, numbers, hyphens, or underscores):', profileId); - return null; - } - // Load configuration let config; try { @@ -180,4 +173,150 @@ export const geospatialTool = ({ - Geographic information lookup`, parameters: geospatialQuerySchema, execute: async ({ query, queryType, includeMap }: { - query: + query: string; + queryType?: string; + includeMap?: boolean; + }) => { + console.log('[GeospatialTool] Execute called with:', { query, queryType, includeMap }); + + const uiFeedbackStream = createStreamableValue(); + uiStream.append(); + + let feedbackMessage = `Processing geospatial query: "${query}". Connecting to mapping service...`; + uiFeedbackStream.update(feedbackMessage); + + const mcpClient = await getConnectedMcpClient(); + + if (!mcpClient) { + feedbackMessage = 'Geospatial functionality is currently unavailable. Please check your configuration and try again.'; + uiFeedbackStream.update(feedbackMessage); + uiFeedbackStream.done(); + uiStream.update(); + return { + type: 'MAP_QUERY_TRIGGER', + originalUserInput: query, + timestamp: new Date().toISOString(), + mcp_response: null, + error: 'MCP client initialization failed - check environment variables and network connectivity', + }; + } + + let mcpData: { + location: { + latitude?: number; + longitude?: number; + place_name?: string; + address?: string; + }; + mapUrl?: string; + } | null = null; + let toolError: string | null = null; + + try { + feedbackMessage = `Connected to mapping service. Processing "${query}"...`; + uiFeedbackStream.update(feedbackMessage); + uiStream.update(); + + const toolName = queryType === 'directions' ? 'mapbox_directions' : 'mapbox_geocoding'; + const toolArgs = { + searchText: query, + includeMapPreview: includeMap !== false, + }; + + console.log('[GeospatialTool] Calling tool:', toolName, 'with args:', toolArgs); + + // Retry logic for tool call + const MAX_RETRIES = 3; + let retryCount = 0; + let geocodeResultUnknown; + while (retryCount < MAX_RETRIES) { + try { + geocodeResultUnknown = await Promise.race([ + mcpClient.callTool({ name: toolName, arguments: toolArgs }), + new Promise((_, reject) => { + setTimeout(() => reject(new Error('Tool call timeout after 30 seconds')), 30000); + }), + ]); + break; + } catch (error: any) { + retryCount++; + if (retryCount === MAX_RETRIES) { + throw error; + } + console.warn(`[GeospatialTool] Retry ${retryCount}/${MAX_RETRIES} after error: ${error.message}`); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + console.log('[GeospatialTool] Raw tool result:', geocodeResultUnknown); + + const geocodeResult = geocodeResultUnknown as { tool_results?: Array<{ content?: unknown }> }; + const toolResults = Array.isArray(geocodeResult.tool_results) ? geocodeResult.tool_results : []; + + if (toolResults.length === 0 || !toolResults[0]?.content) { + throw new Error('No content returned from mapping service'); + } + + let content = toolResults[0].content; + + if (typeof content === 'string') { + const jsonRegex = /```(?:json)?\n?([\s\S]*?)\n?```/; + const match = content.match(jsonRegex); + if (match) { + content = match[1].trim(); + } + + try { + if (typeof content === 'string') { + content = JSON.parse(content); + } + } catch (parseError) { + console.warn('[GeospatialTool] Content is not JSON, using as string:', content); + } + } + + if (typeof content === 'object' && content !== null) { + const parsedData = content as any; + + if (parsedData.location) { + mcpData = { + location: { + latitude: parsedData.location.latitude, + longitude: parsedData.location.longitude, + place_name: parsedData.location.place_name || parsedData.location.name, + address: parsedData.location.address || parsedData.location.formatted_address, + }, + mapUrl: parsedData.mapUrl || parsedData.map_url, + }; + } else { + throw new Error("Response missing required 'location' field"); + } + } else { + throw new Error("Unexpected response format from mapping service"); + } + + feedbackMessage = `Successfully processed location query for: ${mcpData.location.place_name || query}`; + uiFeedbackStream.update(feedbackMessage); + + } catch (error: any) { + console.error('[GeospatialTool] Tool execution failed:', error.message); + console.error('[GeospatialTool] Error stack:', error.stack); + toolError = `Mapping service error: ${error.message}`; + feedbackMessage = toolError; + uiFeedbackStream.update(feedbackMessage); + } finally { + await closeClient(mcpClient); + uiFeedbackStream.done(); + uiStream.update(); + } + + return { + type: 'MAP_QUERY_TRIGGER', + originalUserInput: query, + queryType: queryType || 'geocode', + timestamp: new Date().toISOString(), + mcp_response: mcpData, + error: toolError, + }; + }, +});