diff --git a/.env.local.example b/.env.local.example index 928ed6f6..1934b0c0 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,18 +1,27 @@ -# Mapbox MCP Server Credentials -# Replace with your actual Smithery profile ID and API key -SMITHERY_PROFILE_ID="your_smithery_profile_id_here" -SMITHERY_API_KEY="your_smithery_api_key_here" +# Composio Mapbox Integration +# Replace with your actual Composio auth config ID, user ID, and API key +COMPOSIO_API_KEY=your_composio_api_key +COMPOSIO_MAPBOX_AUTH_CONFIG_ID=ac_YOUR_MAPBOX_CONFIG_ID +COMPOSIO_USER_ID=user@example.com + +# Mapbox Access Token +MAPBOX_ACCESS_TOKEN=your_mapbox_api_key + +# For client-side usage (if needed) +NEXT_PUBLIC_COMPOSIO_API_KEY=your_composio_api_key +NEXT_PUBLIC_COMPOSIO_MAPBOX_AUTH_CONFIG_ID=ac_YOUR_MAPBOX_CONFIG_ID +NEXT_PUBLIC_COMPOSIO_USER_ID=user@example.com # NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is already used by mapbox-map.tsx # Ensure it's also in your .env.local file if you haven't set it up yet. -# NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN="your_mapbox_public_token_here" +# NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN=your_mapbox_public_token_here # AI Provider API Keys # Gemini 3 Pro (Google Generative AI) -GEMINI_3_PRO_API_KEY="your_gemini_3_pro_api_key_here" +GEMINI_3_PRO_API_KEY=your_gemini_3_pro_api_key_here # Supabase Credentials -NEXT_PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL_HERE" -NEXT_PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_ANON_KEY_HERE" -SUPABASE_SERVICE_ROLE_KEY="YOUR_SUPABASE_SERVICE_ROLE_KEY_HERE" -DATABASE_URL="postgresql://postgres:[YOUR-POSTGRES-PASSWORD]@[YOUR-SUPABASE-DB-HOST]:[PORT]/postgres" +NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL_HERE +NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY_HERE +SUPABASE_SERVICE_ROLE_KEY=YOUR_SUPABASE_SERVICE_ROLE_KEY_HERE +DATABASE_URL=postgresql://postgres:[YOUR-POSTGRES-PASSWORD]@[YOUR-SUPABASE-DB-HOST]:[PORT]/postgres diff --git a/COMPOSIO_MIGRATION.md b/COMPOSIO_MIGRATION.md new file mode 100644 index 00000000..6c8f7e53 --- /dev/null +++ b/COMPOSIO_MIGRATION.md @@ -0,0 +1,253 @@ +# Migration Guide: Smithery to Composio + +This document outlines the migration from Smithery to Composio for the Mapbox integration in QCX. + +## Overview + +The QCX project has migrated from using Smithery's MCP server hosting to Composio's integration platform for Mapbox functionality. This change provides better scalability, more robust authentication, and improved tool management. + +## What Changed + +### 1. Dependencies + +**Removed:** +- `@smithery/cli` (^1.2.5) +- `@smithery/sdk` (^1.0.4) +- `smithery` (^0.5.2) + +**Added:** +- `@composio/core` (^0.5.0) + +### 2. Environment Variables + +**Old (Smithery):** +```bash +SMITHERY_PROFILE_ID="your_smithery_profile_id_here" +SMITHERY_API_KEY="your_smithery_api_key_here" +NEXT_PUBLIC_SMITHERY_PROFILE_ID="your_smithery_profile_id_here" +NEXT_PUBLIC_SMITHERY_API_KEY="your_smithery_api_key_here" +``` + +**New (Composio):** +```bash +COMPOSIO_MAPBOX_AUTH_CONFIG_ID="ac_YOUR_MAPBOX_CONFIG_ID" +COMPOSIO_USER_ID="user@example.com" +MAPBOX_ACCESS_TOKEN="your_mapbox_api_key" +NEXT_PUBLIC_COMPOSIO_MAPBOX_AUTH_CONFIG_ID="ac_YOUR_MAPBOX_CONFIG_ID" +NEXT_PUBLIC_COMPOSIO_USER_ID="user@example.com" +``` + +### 3. Configuration Files + +**mapbox_mcp_config.json** + +**Old:** +```json +{ + "mcpServers": { + "mapbox-mcp-server": { + "command": "npx", + "args": [ + "-y", + "@smithery/cli@latest", + "run", + "@ngoiyaeric/mapbox-mcp-server", + "--key", + "705b0222-a657-4cd2-b180-80c406cf6179", + "--profile", + "smooth-lemur-vfUbUE" + ] + } + } +} +``` + +**New:** +```json +{ + "composio": { + "mapbox": { + "authConfigId": "ac_YOUR_MAPBOX_CONFIG_ID", + "userId": "user@example.com", + "description": "Composio configuration for Mapbox integration" + } + } +} +``` + +### 4. Code Changes + +#### mapbox_mcp/hooks.ts + +**Old Connection Method:** +```typescript +const mcp = useMcp({ + url: `https://server.smithery.ai/@Waldzell-Agentics/mcp-server/mcp?profile=${process.env.NEXT_PUBLIC_SMITHERY_PROFILE_ID}&api_key=${process.env.NEXT_PUBLIC_SMITHERY_API_KEY}`, + debug: process.env.NODE_ENV === 'development', + autoReconnect: true, + autoRetry: 5000, +}); +``` + +**New Connection Method:** +```typescript +const composioClient = getComposioClient(); +const { connectionId, connectedAccount } = await initializeComposioMapbox(); +``` + +#### Tool Execution + +**Old:** +```typescript +const result = await mcp.callTool('geocode_location', { + query: address, + includeMapPreview: true, +}); +``` + +**New:** +```typescript +const result = await composioClient.executeAction({ + action: 'mapbox_geocode_location', + params: { + query: address, + includeMapPreview: true, + }, + connectedAccountId: connectionId, +}); +``` + +## Migration Steps + +### Step 1: Install Composio + +```bash +bun install @composio/core +``` + +### Step 2: Remove Smithery Dependencies + +```bash +bun remove @smithery/cli @smithery/sdk smithery +``` + +### Step 3: Set Up Composio Account + +1. Sign up at https://composio.dev +2. Create a new auth config for Mapbox +3. Select "API Key" as the authentication method +4. Note your auth config ID (starts with `ac_`) + +### Step 4: Update Environment Variables + +1. Copy `.env.local.example` to `.env.local` (if not already done) +2. Replace Smithery variables with Composio variables: + ```bash + COMPOSIO_MAPBOX_AUTH_CONFIG_ID="ac_YOUR_ACTUAL_CONFIG_ID" + COMPOSIO_USER_ID="your_email@example.com" + MAPBOX_ACCESS_TOKEN="your_mapbox_token" + ``` + +### Step 5: Update Code References + +The following files have been updated automatically: +- `mapbox_mcp/composio-mapbox.ts` (new file) +- `mapbox_mcp/hooks.ts` (updated) +- `mapbox_mcp/index.ts` (updated) +- `mapbox_mcp_config.json` (updated) +- `package.json` (updated) +- `.env.local.example` (updated) + +### Step 6: Test the Integration + +```bash +# Test the connection +bun run mapbox_mcp/index.ts + +# Run the development server +bun run dev +``` + +## API Compatibility + +The `useMCPMapClient` hook maintains the same interface, so existing components using it should continue to work without changes: + +```typescript +const { + isConnected, + isLoading, + error, + connect, + disconnect, + processLocationQuery, + geocodeLocation, + calculateDistance, + searchNearbyPlaces, +} = useMCPMapClient(); +``` + +## Troubleshooting + +### Issue: "Composio client not connected" + +**Solution:** Ensure you've called `connect()` before using any tool functions: + +```typescript +useEffect(() => { + connect(); +}, [connect]); +``` + +### Issue: "Invalid auth config ID" + +**Solution:** Verify your `COMPOSIO_MAPBOX_AUTH_CONFIG_ID` starts with `ac_` and is copied correctly from the Composio dashboard. + +### Issue: "Mapbox API key invalid" + +**Solution:** Check that your `MAPBOX_ACCESS_TOKEN` is valid and has the necessary scopes enabled in your Mapbox account. + +### Issue: Tool execution fails + +**Solution:** Verify the action names match Composio's Mapbox integration. Common actions: +- `mapbox_geocode_location` +- `mapbox_calculate_distance` +- `mapbox_search_nearby_places` +- `mapbox_generate_map_link` + +## Benefits of Composio + +1. **Better Authentication Management**: Centralized auth config management +2. **Improved Security**: API keys stored securely in Composio +3. **Scalability**: Better handling of multiple integrations +4. **Monitoring**: Built-in logging and monitoring in Composio dashboard +5. **Flexibility**: Easier to add new tools and integrations + +## Resources + +- [Composio Documentation](https://docs.composio.dev) +- [Composio GitHub](https://github.com/ComposioHQ/composio) +- [Mapbox API Documentation](https://docs.mapbox.com) +- [QCX Documentation](https://deepwiki.com/QueueLab/QCX) + +## Support + +If you encounter issues during migration: +1. Check the Composio dashboard for connection status +2. Review the logs in your development console +3. Consult the [mapbox_mcp/README.md](./mapbox_mcp/README.md) file +4. Open an issue in the QCX repository + +## Rollback + +If you need to rollback to Smithery: + +```bash +# Reinstall Smithery packages +bun install @smithery/cli@^1.2.5 @smithery/sdk@^1.0.4 smithery@^0.5.2 + +# Restore old environment variables in .env.local +# Restore old code from git history +git checkout HEAD~1 -- mapbox_mcp/ +``` + +However, we recommend staying with Composio for the improved features and maintainability. diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 1a544fe8..729ecc0f 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -6,7 +6,7 @@ import { BotMessage } from '@/components/message'; import { geospatialQuerySchema } from '@/lib/schema/geospatial'; import { Client as MCPClientClass } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; -import { createSmitheryUrl } from '@smithery/sdk'; +// Smithery SDK removed - using direct URL construction import { z } from 'zod'; // Types @@ -34,17 +34,17 @@ interface MapboxConfig { * Establish connection to the MCP server with proper environment validation. */ async function getConnectedMcpClient(): Promise { - const apiKey = process.env.NEXT_PUBLIC_SMITHERY_API_KEY; - const mapboxAccessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN; - const profileId = process.env.NEXT_PUBLIC_SMITHERY_PROFILE_ID; + const composioApiKey = process.env.COMPOSIO_API_KEY; + const mapboxAccessToken = process.env.MAPBOX_ACCESS_TOKEN; + const composioUserId = process.env.COMPOSIO_USER_ID; console.log('[GeospatialTool] Environment check:', { - apiKey: apiKey ? `${apiKey.substring(0, 8)}...` : 'MISSING', + composioApiKey: composioApiKey ? `${composioApiKey.substring(0, 8)}...` : 'MISSING', mapboxAccessToken: mapboxAccessToken ? `${mapboxAccessToken.substring(0, 8)}...` : 'MISSING', - profileId: profileId ? `${profileId.substring(0, 8)}...` : 'MISSING', + composioUserId: composioUserId ? `${composioUserId.substring(0, 8)}...` : 'MISSING', }); - if (!apiKey || !mapboxAccessToken || !profileId || !apiKey.trim() || !mapboxAccessToken.trim() || !profileId.trim()) { + if (!composioApiKey || !mapboxAccessToken || !composioUserId || !composioApiKey.trim() || !mapboxAccessToken.trim() || !composioUserId.trim()) { console.error('[GeospatialTool] Missing or empty required environment variables'); return null; } @@ -67,20 +67,25 @@ async function getConnectedMcpClient(): Promise { console.log('[GeospatialTool] Using fallback config'); } - // Build Smithery URL - const smitheryUrlOptions = { config, apiKey, profileId }; - const mcpServerBaseUrl = `https://server.smithery.ai/@Waldzell-Agentics/mcp-server/mcp?api_key=${smitheryUrlOptions.apiKey}&profile=${smitheryUrlOptions.profileId}`; - let serverUrlToUse; + // Build Composio MCP server URL + // Note: This should be migrated to use Composio SDK directly instead of MCP client + // For now, constructing URL directly without Smithery SDK + let serverUrlToUse: URL; try { - serverUrlToUse = createSmitheryUrl(mcpServerBaseUrl, smitheryUrlOptions); + // Construct URL with Composio credentials + const baseUrl = 'https://api.composio.dev/v1/mcp/mapbox'; + serverUrlToUse = new URL(baseUrl); + serverUrlToUse.searchParams.set('api_key', composioApiKey); + serverUrlToUse.searchParams.set('user_id', composioUserId); + const urlDisplay = serverUrlToUse.toString().split('?')[0]; - console.log('[GeospatialTool] MCP Server URL created:', urlDisplay); + console.log('[GeospatialTool] Composio MCP Server URL created:', urlDisplay); if (!serverUrlToUse.href || !serverUrlToUse.href.startsWith('https://')) { throw new Error('Invalid server URL generated'); } } catch (urlError: any) { - console.error('[GeospatialTool] Error creating Smithery URL:', urlError.message); + console.error('[GeospatialTool] Error creating Composio URL:', urlError.message); return null; } diff --git a/mapbox_mcp/README.md b/mapbox_mcp/README.md new file mode 100644 index 00000000..02d20223 --- /dev/null +++ b/mapbox_mcp/README.md @@ -0,0 +1,203 @@ +# Mapbox Integration with Composio + +This directory contains the Composio-based Mapbox integration for QCX, replacing the previous Smithery implementation. + +## Overview + +The integration uses [Composio](https://composio.dev) to manage authentication and tool execution for Mapbox services. This provides a more robust and scalable approach to integrating external services. + +## ⚠️ Security Warning + +**IMPORTANT**: The Composio integration requires server-side environment variables (`MAPBOX_ACCESS_TOKEN`) that should **NEVER** be exposed to the client. + +- The `useMCPMapClient` hook should **NOT** be used directly in client components +- Instead, create server-side API routes that handle Composio authentication and tool execution +- Only expose necessary data to the client through your API routes + +## Files + +- **composio-mapbox.ts**: Core Composio client setup and authentication logic (server-side only) +- **hooks.ts**: React hooks for using Mapbox functionality (should be used server-side or via API routes) +- **index.ts**: Test script for verifying the Composio connection +- **README.md**: This file + +## Environment Variables + +Set the following environment variables in your `.env.local` file: + +```bash +# Composio Configuration (Server-side only) +COMPOSIO_MAPBOX_AUTH_CONFIG_ID=ac_YOUR_MAPBOX_CONFIG_ID +COMPOSIO_USER_ID=user@example.com + +# Mapbox Access Token (Server-side only - DO NOT expose to client) +MAPBOX_ACCESS_TOKEN=your_mapbox_api_key + +# For client-side usage (if needed for display purposes only) +NEXT_PUBLIC_COMPOSIO_MAPBOX_AUTH_CONFIG_ID=ac_YOUR_MAPBOX_CONFIG_ID +NEXT_PUBLIC_COMPOSIO_USER_ID=user@example.com +``` + +**Note**: All environment variables are **required**. The application will fail fast with clear error messages if any are missing. + +## Setup + +1. **Install Composio SDK**: + ```bash + bun install @composio/core + ``` + +2. **Create Mapbox Auth Config in Composio**: + - Sign up for a Composio account at https://composio.dev + - Create an auth config for Mapbox with API Key authentication + - Note the auth config ID (starts with `ac_`) + +3. **Set Environment Variables**: + - Copy the values from your Composio dashboard + - Add your Mapbox access token from https://account.mapbox.com + - **Never commit `.env.local` to version control** + +4. **Test the Connection**: + ```bash + bun run mapbox_mcp/index.ts + ``` + +## Usage + +### Server-Side API Route (Recommended) + +Create a server-side API route to handle Mapbox operations: + +```typescript +// app/api/mapbox/geocode/route.ts +import { initializeComposioMapbox, getComposioClient } from '@/mapbox_mcp/composio-mapbox'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(request: NextRequest) { + try { + const { address } = await request.json(); + + // Initialize Composio connection (server-side) + const { connectionId } = await initializeComposioMapbox(); + const composioClient = getComposioClient(); + + // Execute geocoding action + const result = await composioClient.executeAction({ + action: 'mapbox_geocode_location', + params: { + query: address, + includeMapPreview: true, + }, + connectedAccountId: connectionId, + }); + + return NextResponse.json(result.data); + } catch (error) { + console.error('Geocoding error:', error); + return NextResponse.json( + { error: 'Failed to geocode location' }, + { status: 500 } + ); + } +} +``` + +### Client-Side Usage + +Call your API route from client components: + +```typescript +// components/MapSearch.tsx +'use client'; + +import { useState } from 'react'; + +export function MapSearch() { + const [result, setResult] = useState(null); + + const handleGeocode = async (address: string) => { + try { + const response = await fetch('/api/mapbox/geocode', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ address }), + }); + + const data = await response.json(); + setResult(data); + } catch (error) { + console.error('Geocoding failed:', error); + } + }; + + return ( +
+ + {result &&
{JSON.stringify(result, null, 2)}
} +
+ ); +} +``` + +## Available Composio Actions + +The following Mapbox actions are available through Composio: + +- **mapbox_geocode_location**: Convert address to coordinates +- **mapbox_calculate_distance**: Calculate distance between two locations +- **mapbox_search_nearby_places**: Search for nearby places +- **mapbox_generate_map_link**: Generate map links + +**Note**: Action names use the `mapbox_` prefix (e.g., `mapbox_geocode_location`), not the old MCP names (e.g., `geocode_location`). + +## Migration from Smithery + +The following changes were made to migrate from Smithery to Composio: + +1. **Dependencies**: Replaced `@smithery/cli` and `@smithery/sdk` with `@composio/core` +2. **Authentication**: Changed from Smithery SSE transport to Composio's API Key authentication +3. **Tool Execution**: Updated tool calls to use Composio's `executeAction` method +4. **Configuration**: Replaced `mapbox_mcp_config.json` with Composio-specific configuration +5. **Security**: Added proper environment variable validation and fail-fast behavior + +## Troubleshooting + +### Connection Errors + +- Verify all environment variables are set correctly in `.env.local` +- Check that your Composio auth config ID is valid +- Ensure your Mapbox access token has the necessary permissions +- Review error messages - the application will clearly indicate which variable is missing + +### Tool Execution Errors + +- Verify the action names use the `mapbox_` prefix +- Check that the connection ID is valid +- Review Composio logs in the dashboard +- Ensure parameters match the expected format + +### Environment Variable Errors + +If you see errors about missing environment variables: +- Check that all required variables are set in `.env.local` +- Restart your development server after adding variables +- Verify variable names match exactly (case-sensitive) + +## Resources + +- [Composio Documentation](https://docs.composio.dev) +- [Mapbox API Documentation](https://docs.mapbox.com) +- [QCX Documentation](https://deepwiki.com/QueueLab/QCX) +- [Migration Guide](../COMPOSIO_MIGRATION.md) + +## Security Best Practices + +1. **Never expose server-side environment variables to the client** +2. **Always use API routes for Composio operations** +3. **Validate and sanitize user input before passing to Composio** +4. **Implement rate limiting on your API routes** +5. **Monitor Composio usage through the dashboard** +6. **Rotate API keys regularly** +7. **Use environment-specific credentials (dev, staging, prod)** diff --git a/mapbox_mcp/composio-mapbox.ts b/mapbox_mcp/composio-mapbox.ts new file mode 100644 index 00000000..2737fb37 --- /dev/null +++ b/mapbox_mcp/composio-mapbox.ts @@ -0,0 +1,132 @@ +import { Composio } from '@composio/core'; +import { AuthScheme } from '@composio/core'; + +/** + * Validate required environment variables + * @throws Error if any required environment variable is missing + */ +function validateEnvironmentVariables(): { + authConfigId: string; + userId: string; + mapboxToken: string; + apiKey: string; +} { + const authConfigId = process.env.COMPOSIO_MAPBOX_AUTH_CONFIG_ID; + const userId = process.env.COMPOSIO_USER_ID; + const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN; + const apiKey = process.env.COMPOSIO_API_KEY; + + if (!authConfigId) { + throw new Error( + 'COMPOSIO_MAPBOX_AUTH_CONFIG_ID environment variable is required. ' + + 'Please set it in your .env.local file.' + ); + } + + if (!userId) { + throw new Error( + 'COMPOSIO_USER_ID environment variable is required. ' + + 'Please set it in your .env.local file.' + ); + } + + if (!mapboxToken) { + throw new Error( + 'MAPBOX_ACCESS_TOKEN environment variable is required. ' + + 'Please set it in your .env.local file.' + ); + } + + if (!apiKey) { + throw new Error( + 'COMPOSIO_API_KEY environment variable is required. ' + + 'Please set it in your .env.local file.' + ); + } + + return { authConfigId, userId, mapboxToken, apiKey }; +} + +/** + * Authenticate Mapbox toolkit using Composio + * This should only be called server-side to avoid exposing API keys + * @param userId - User ID from database/application + * @param authConfigId - Auth config ID for Mapbox + * @param mapboxApiKey - Mapbox API key (should be retrieved securely) + * @param composioApiKey - Composio API key for SDK authentication + * @returns Connection ID + */ +export async function authenticateToolkit( + userId: string, + authConfigId: string, + mapboxApiKey: string, + composioApiKey: string +): Promise { + if (!userId || !authConfigId || !mapboxApiKey || !composioApiKey) { + throw new Error( + 'userId, authConfigId, mapboxApiKey, and composioApiKey are required for authentication' + ); + } + + // Initialize Composio with API key + const composio = new Composio({ apiKey: composioApiKey }); + + const connectionRequest = await composio.connectedAccounts.initiate( + userId, + authConfigId, + { + config: AuthScheme.APIKey({ + api_key: mapboxApiKey + }) + } + ); + + // API Key authentication is immediate - no redirect needed + console.log(`Successfully connected Mapbox for user ${userId}`); + console.log(`Connection status: ${connectionRequest.status}`); + + return connectionRequest.id; +} + +/** + * Initialize Composio connection for Mapbox + * This should only be called server-side to avoid exposing API keys + * @throws Error if environment variables are missing or connection fails + */ +export async function initializeComposioMapbox() { + const { authConfigId, userId, mapboxToken, apiKey } = validateEnvironmentVariables(); + + try { + // Authenticate the toolkit + const connectionId = await authenticateToolkit(userId, authConfigId, mapboxToken, apiKey); + + // Initialize Composio client with API key for subsequent operations + const composio = new Composio({ apiKey }); + + // Verify the connection + const connectedAccount = await composio.connectedAccounts.get(connectionId); + console.log("Connected account:", connectedAccount); + + return { connectionId, connectedAccount, composio }; + } catch (error) { + console.error("Failed to initialize Composio Mapbox connection:", error); + throw error; + } +} + +/** + * Get Composio instance for Mapbox operations + * Requires COMPOSIO_API_KEY environment variable + */ +export function getComposioClient() { + const apiKey = process.env.COMPOSIO_API_KEY; + + if (!apiKey) { + throw new Error( + 'COMPOSIO_API_KEY environment variable is required. ' + + 'Please set it in your .env.local file.' + ); + } + + return new Composio({ apiKey }); +} diff --git a/mapbox_mcp/hooks.ts b/mapbox_mcp/hooks.ts index 06342b3f..ae317f51 100644 --- a/mapbox_mcp/hooks.ts +++ b/mapbox_mcp/hooks.ts @@ -1,14 +1,12 @@ -import { useState, useCallback, useRef } from 'react'; +import { useState, useCallback, useRef, useEffect } from 'react'; import { generateText } from 'ai'; -import { useMcp } from 'use-mcp/react'; - +import { getModel } from '@/lib/utils'; // Define Tool type locally if needed type Tool = { name: string; // Add other properties as needed based on your usage }; -import { getModel } from '@/lib/utils'; // Types for location and mapping data interface LocationResult { @@ -41,57 +39,90 @@ interface PlaceResult { } /** - * Custom React hook to interact with the Mapbox MCP server. + * Custom React hook to interact with Mapbox via Composio. * Manages client connection, tool invocation, and state (loading, error, connection status). - * Uses `useMcp` from 'use-mcp/react' for communication. + * + * WARNING: This hook should NOT be used directly in client components as it requires + * server-side environment variables (MAPBOX_ACCESS_TOKEN). Instead, create a server-side + * API route that handles Composio authentication and tool execution. */ export const useMCPMapClient = () => { const [isConnected, setIsConnected] = useState(false); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [connectionId, setConnectionId] = useState(null); - // Refs to hold available tools + // Refs to hold available tools and Composio client const toolsRef = useRef(null); + const composioClientRef = useRef(null); - // Configure MCP client using useMcp hook - const mcp = useMcp({ - //https://server.smithery.ai/@Waldzell-Agentics/mcp-server/mcp - url: `https://server.smithery.ai/@Waldzell-Agentics/mcp-server/mcp?profile=${process.env.NEXT_PUBLIC_SMITHERY_PROFILE_ID}&api_key=${process.env.NEXT_PUBLIC_SMITHERY_API_KEY}`, - debug: process.env.NODE_ENV === 'development', - autoReconnect: true, - autoRetry: 5000, - }); - - // Update connection status based on MCP state - const connect = useCallback(async () => { - if (mcp.state === 'ready') { + // Initialize Composio client on mount + useEffect(() => { + // Import dynamically to avoid bundling server-side code in client + const initClient = async () => { try { - setIsLoading(true); - setError(null); - toolsRef.current = mcp.tools; - setIsConnected(true); - console.log('✅ Connected to MCP server'); - console.log('Available tools:', mcp.tools.map((tool: Tool) => tool.name)); + const { getComposioClient } = await import('./composio-mapbox'); + composioClientRef.current = getComposioClient(); } catch (err) { - setError(`Failed to connect to MCP server: ${err}`); - console.error('❌ MCP connection error:', err); - } finally { - setIsLoading(false); + console.error('Failed to initialize Composio client:', err); + setError('Failed to initialize Composio client. This may be due to missing environment variables.'); } + }; + initClient(); + }, []); + + // Connect to Composio Mapbox - should be called from server-side + const connect = useCallback(async () => { + try { + setIsLoading(true); + setError(null); + + // Import server-side function + const { initializeComposioMapbox } = await import('./composio-mapbox'); + const { connectionId: connId, connectedAccount } = await initializeComposioMapbox(); + setConnectionId(connId); + + // Get available tools from Composio + if (composioClientRef.current) { + const tools = await composioClientRef.current.getTools({ + apps: ['mapbox'] + }); + toolsRef.current = tools; + } + + setIsConnected(true); + console.log('✅ Connected to Composio Mapbox'); + console.log('Connection ID:', connId); + } catch (err) { + const errorMessage = `Failed to connect to Composio Mapbox: ${err}`; + setError(errorMessage); + console.error('❌ Composio connection error:', err); + throw new Error(errorMessage); + } finally { + setIsLoading(false); } - }, [mcp.state, mcp.tools]); + }, []); // Empty deps - function doesn't depend on external state const disconnect = useCallback(async () => { - if (mcp.state === 'ready') { - await mcp.disconnect(); + try { + // Clean up Composio connection if it exists + if (composioClientRef.current && connectionId) { + // Note: Composio SDK may not have a direct disconnect method for connected accounts + // This is a placeholder for proper cleanup if the SDK provides it + console.log('Disconnecting from Composio...'); + } + toolsRef.current = null; setIsConnected(false); + setConnectionId(null); + } catch (err) { + console.error('Error during disconnect:', err); } - }, [mcp.state]); + }, [connectionId]); // Depends on connectionId const processLocationQuery = useCallback(async (query: string) => { - if (mcp.state !== 'ready' || !toolsRef.current) { - throw new Error('MCP client not connected'); + if (!isConnected || !toolsRef.current) { + throw new Error('Composio client not connected'); } setIsLoading(true); setError(null); @@ -100,11 +131,12 @@ export const useMCPMapClient = () => { model: getModel(), tools: toolsRef.current, system: `You are an expert location data processing engine. Your role is to accurately use the available tools to answer location-based queries and provide structured data. + Available tools and their purpose: -- geocode_location: Converts addresses or place names to geographic coordinates. Also provides a map preview URL for the location. -- calculate_distance: Calculates the travel distance and duration between two locations for various profiles (driving, walking, cycling). Also provides a map preview URL for the route. -- search_nearby_places: Searches for points of interest (e.g., 'restaurants', 'gas stations') near a specified location. Provides details for each place including a map preview URL. -- generate_map_link: Generates static and interactive map links for a given location. +- mapbox_geocode_location: Converts addresses or place names to geographic coordinates. Also provides a map preview URL for the location. +- mapbox_calculate_distance: Calculates the travel distance and duration between two locations for various profiles (driving, walking, cycling). Also provides a map preview URL for the route. +- mapbox_search_nearby_places: Searches for points of interest (e.g., 'restaurants', 'gas stations') near a specified location. Provides details for each place including a map preview URL. +- mapbox_generate_map_link: Generates static and interactive map links for a given location. For any user query, determine the most appropriate tool or sequence of tools to achieve the user's goal. Prioritize calling tools to get structured data. The text response you generate should summarize the findings and must include any relevant map URLs or key information provided by the tools. @@ -149,68 +181,92 @@ Focus on extracting and presenting factual data from the tools.`, shouldShowMap, }; } catch (err) { - setError(`Query processing failed: ${err}`); - throw err; + const errorMessage = `Query processing failed: ${err}`; + setError(errorMessage); + throw new Error(errorMessage); } finally { setIsLoading(false); } - }, [mcp.state, mcp.tools]); + }, [isConnected]); // Depends on isConnected const geocodeLocation = useCallback(async (address: string): Promise => { - if (mcp.state !== 'ready') { - throw new Error('MCP client not connected'); + if (!isConnected || !composioClientRef.current || !connectionId) { + throw new Error('Composio client not connected'); } try { - const result = await mcp.callTool('geocode_location', { - query: address, - includeMapPreview: true, + const result = await composioClientRef.current.executeAction({ + action: 'mapbox_geocode_location', + params: { + query: address, + includeMapPreview: true, + }, + connectedAccountId: connectionId, }); - const match = result.content[1]?.text?.match(/```json\n([\s\S]*?)\n```/); - return JSON.parse(match?.[1] || '{}'); + return result.data; } catch (err) { console.error('Geocoding error:', err); - setError(`Geocoding error: ${err}`); - throw err; + const errorMessage = `Geocoding error: ${err}`; + setError(errorMessage); + throw new Error(errorMessage); } - }, [mcp.state, mcp.callTool]); + }, [isConnected, connectionId]); // Depends on isConnected and connectionId - const calculateDistance = useCallback(async (from: string, to: string, profile: 'driving' | 'walking' | 'cycling' = 'driving'): Promise => { - if (mcp.state !== 'ready') { - throw new Error('MCP client not connected'); + const calculateDistance = useCallback(async ( + from: string, + to: string, + profile: 'driving' | 'walking' | 'cycling' = 'driving' + ): Promise => { + if (!isConnected || !composioClientRef.current || !connectionId) { + throw new Error('Composio client not connected'); } try { - const result = await mcp.callTool('calculate_distance', { - from, - to, - profile, - includeRouteMap: true, + const result = await composioClientRef.current.executeAction({ + action: 'mapbox_calculate_distance', + params: { + from, + to, + profile, + includeRouteMap: true, + }, + connectedAccountId: connectionId, }); - return JSON.parse(result.content[1]?.text?.match(/```json\n(.*?)\n```/s)?.[1] || '{}'); + return result.data; } catch (err) { console.error('Distance calculation error:', err); - setError(`Distance calculation error: ${err}`); - throw err; + const errorMessage = `Distance calculation error: ${err}`; + setError(errorMessage); + throw new Error(errorMessage); } - }, [mcp.state, mcp.callTool]); + }, [isConnected, connectionId]); // Depends on isConnected and connectionId - const searchNearbyPlaces = useCallback(async (location: string, query: string, radius: number = 1000, limit: number = 5): Promise => { - if (mcp.state !== 'ready') { - throw new Error('MCP client not connected'); + const searchNearbyPlaces = useCallback(async ( + location: string, + query: string, + radius: number = 1000, + limit: number = 5 + ): Promise => { + if (!isConnected || !composioClientRef.current || !connectionId) { + throw new Error('Composio client not connected'); } try { - const result = await mcp.callTool('search_nearby_places', { - location, - query, - radius, - limit, + const result = await composioClientRef.current.executeAction({ + action: 'mapbox_search_nearby_places', + params: { + location, + query, + radius, + limit, + }, + connectedAccountId: connectionId, }); - return JSON.parse(result.content[1]?.text?.match(/```json\n(.*?)\n```/s)?.[1] || '{}'); + return result.data; } catch (err) { console.error('Places search error:', err); - setError(`Places search error: ${err}`); - throw err; + const errorMessage = `Places search error: ${err}`; + setError(errorMessage); + throw new Error(errorMessage); } - }, [mcp.state, mcp.callTool]); + }, [isConnected, connectionId]); // Depends on isConnected and connectionId return { isConnected, diff --git a/mapbox_mcp/index.ts b/mapbox_mcp/index.ts index f11f7577..cca0b104 100644 --- a/mapbox_mcp/index.ts +++ b/mapbox_mcp/index.ts @@ -1,92 +1,59 @@ -import type { Tool } from 'use-mcp/react'; -import { useMcp } from 'use-mcp/react'; +import { getComposioClient, initializeComposioMapbox } from './composio-mapbox'; - -// Environment variables required by this script to connect to the Smithery-hosted MCP server. -// - SMITHERY_PROFILE_ID: Your Smithery profile ID. -// - SMITHERY_API_KEY: Your Smithery API key for authentication. -// Note: The Mapbox Access Token (MAPBOX_ACCESS_TOKEN) is configured on the server-side (on Smithery) -// and is not directly passed by this client script during the connection setup for this particular example. -const profileId = process.env.SMITHERY_PROFILE_ID; -const apiKey = process.env.SMITHERY_API_KEY; -const serverName = "mapbox-mcp-server"; // The unique name of your MCP server deployed on Smithery. - -async function testMCPConnection() { - // Check for required environment variables for Smithery connection. - if (!profileId || !apiKey) { - console.error("SMITHERY_PROFILE_ID and SMITHERY_API_KEY environment variables are required for this script."); - return; // Return early if essential credentials are missing. - } - - // Construct the server URL for SSE (Server-Sent Events) transport. - const serverUrl = `https://server.smithery.ai/${serverName}/mcp?profile=${profileId}&api_key=${apiKey}`; - - // Declare client variable for cleanup in finally block. - let client: any; // Type would ideally be defined by use-mcp's Node.js client type. +async function testComposioConnection() { + let composioClient: any; try { - // Log the connection attempt (masking API key for security). - const urlToLog = serverUrl.split('?')[0] + `?profile=${profileId}&api_key=****`; - console.log(`Attempting to connect to MCP server at ${urlToLog}...`); + console.log(`Attempting to connect to Composio Mapbox...`); - // Initialize the MCP client using createMcpClient (assumed Node.js equivalent of useMcp). - client = await useMcp({ - url: serverUrl, - autoReconnect: true, - autoRetry: 5000, - debug: process.env.NODE_ENV === 'development', - }); + // Initialize the Composio client and authenticate + // This will validate environment variables and throw if any are missing + const { connectionId, connectedAccount } = await initializeComposioMapbox(); + composioClient = getComposioClient(); - console.log("✅ Successfully connected to MCP server."); + console.log("✅ Successfully connected to Composio Mapbox."); + console.log("Connection ID:", connectionId); - // Fetch and list available tools from the server. - const tools = await client.tools(); - console.log("🛠️ Available tools:", tools.map((tool: Tool) => tool.name)); + // Fetch and list available tools from Composio + const tools = await composioClient.getTools({ + apps: ['mapbox'] + }); + console.log("🛠️ Available tools:", tools.map((tool: any) => tool.name)); - // Perform a sample tool call if 'geocode_location' tool is available. - if (tools.some((tool: Tool) => tool.name === 'geocode_location')) { - console.log("\n📞 Attempting to call 'geocode_location' tool for 'Eiffel Tower'..."); - const geocodeParams = { query: "Eiffel Tower", includeMapPreview: true }; + // Perform a sample tool call if 'mapbox_geocode_location' action is available. + const geocodeAction = tools.find((tool: any) => tool.name === 'mapbox_geocode_location'); + if (geocodeAction) { + console.log("\n📞 Attempting to call 'mapbox_geocode_location' action for 'Eiffel Tower'..."); try { - const geocodeResult = await client.callTool('geocode_location', geocodeParams); + const geocodeResult = await composioClient.executeAction({ + action: 'mapbox_geocode_location', + params: { + query: "Eiffel Tower", + includeMapPreview: true + }, + connectedAccountId: connectionId, + }); - // Parse the structured JSON from the tool result (assumes same server response format). - let resultOutput = geocodeResult; - if (Array.isArray(geocodeResult?.content) && geocodeResult.content.length > 0) { - const lastContentItem = geocodeResult.content[geocodeResult.content.length - 1]; - if (lastContentItem && typeof lastContentItem.text === 'string') { - const jsonMatch = lastContentItem.text.match(/```json\n([\s\S]*?)\n```/); - if (jsonMatch && jsonMatch[1]) { - try { - resultOutput = JSON.parse(jsonMatch[1]); - } catch (parseError) { - console.warn("Could not parse JSON from tool result text block, logging raw text."); - resultOutput = lastContentItem.text; - } - } else { - resultOutput = geocodeResult.content.map((c: any) => c.text).join('\n'); - } - } - } - console.log("🗺️ Geocode Result:", JSON.stringify(resultOutput, null, 2)); + console.log("🗺️ Geocode Result:", JSON.stringify(geocodeResult.data, null, 2)); } catch (toolError) { - console.error("❌ Error calling 'geocode_location':", toolError); + console.error("❌ Error calling 'mapbox_geocode_location':", toolError); } } else { - console.warn("⚠️ 'geocode_location' tool not found, skipping sample call."); + console.warn("⚠️ 'mapbox_geocode_location' action not found, skipping sample call."); } } catch (error) { - console.error("❌ MCP connection or operation failed:", error); - } finally { - // Close the client connection if it exists. - if (client) { - console.log("\nClosing MCP client connection..."); - await client.close(); - console.log("🔌 Client connection closed."); + console.error("❌ Composio connection or operation failed:", error); + if (error instanceof Error) { + console.error("Error message:", error.message); } + process.exit(1); } } -// Run the test connection function. -testMCPConnection(); +// Run the test connection function only if this file is executed directly +if (require.main === module) { + testComposioConnection(); +} + +export { testComposioConnection }; diff --git a/mapbox_mcp_config.json b/mapbox_mcp_config.json index 180d8491..deeedf86 100644 --- a/mapbox_mcp_config.json +++ b/mapbox_mcp_config.json @@ -1,17 +1,9 @@ { - "mcpServers": { - "mapbox-mcp-server": { - "command": "npx", - "args": [ - "-y", - "@smithery/cli@latest", - "run", - "@ngoiyaeric/mapbox-mcp-server", - "--key", - "705b0222-a657-4cd2-b180-80c406cf6179", - "--profile", - "smooth-lemur-vfUbUE" - ] + "composio": { + "mapbox": { + "authConfigId": "ac_YOUR_MAPBOX_CONFIG_ID", + "userId": "user@example.com", + "description": "Composio configuration for Mapbox integration. Set COMPOSIO_MAPBOX_AUTH_CONFIG_ID, COMPOSIO_USER_ID, and MAPBOX_ACCESS_TOKEN environment variables." } } } diff --git a/package.json b/package.json index 92ccfe4d..681d80ce 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,7 @@ "@radix-ui/react-tabs": "^1.1.9", "@radix-ui/react-toast": "^1.2.11", "@radix-ui/react-tooltip": "^1.2.3", - "@smithery/cli": "^1.2.5", - "@smithery/sdk": "^1.0.4", + "@composio/core": "^0.3.3", "@supabase/ssr": "^0.3.0", "@supabase/supabase-js": "^2.0.0", "@tailwindcss/typography": "^0.5.16", @@ -87,7 +86,6 @@ "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", - "smithery": "^0.5.2", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7",