Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Spinner } from '@/components/ui/spinner';
import { Section } from '@/components/section';
import { FollowupPanel } from '@/components/followup-panel';
import { inquire, researcher, taskManager, querySuggestor } from '@/lib/agents';
import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial'; // Added import for the hook
import { writer } from '@/lib/agents/writer';
import { saveChat, getSystemPrompt } from '@/lib/actions/chat'; // Added getSystemPrompt
import { Chat, AIMessage } from '@/lib/types';
Expand All @@ -29,7 +30,7 @@ type RelatedQueries = {
items: { query: string }[];
};

async function submit(formData?: FormData, skip?: boolean) {
async function submit(formData?: FormData, skip?: boolean, mcp?: any) { // Added mcp as a parameter
'use server';

// TODO: Update agent function signatures in lib/agents/researcher.tsx and lib/agents/writer.tsx
Expand Down Expand Up @@ -145,6 +146,7 @@ async function submit(formData?: FormData, skip?: boolean) {
uiStream,
streamText,
messages,
mcp, // Pass mcp to researcher
useSpecificAPI
);
answer = fullResponse;
Expand Down
4 changes: 3 additions & 1 deletion components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useState, useRef } from 'react'
import { useRouter } from 'next/navigation'
import type { AI, UIState } from '@/app/actions'
import { useUIState, useActions } from 'ai/rsc'
import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import
import { cn } from '@/lib/utils'
import { UserMessage } from './user-message'
import { Button } from './ui/button'
Expand All @@ -20,6 +21,7 @@ interface ChatPanelProps {
export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
const [, setMessages] = useUIState<typeof AI>()
const { submit } = useActions()
const mcp = useGeospatialToolMcp() // Call the hook
const [isButtonPressed, setIsButtonPressed] = useState(false)
const [isMobile, setIsMobile] = useState(false)
const inputRef = useRef<HTMLTextAreaElement>(null)
Expand Down Expand Up @@ -56,7 +58,7 @@ export function ChatPanel({ messages, input, setInput }: ChatPanelProps) {
}
])
const formData = new FormData(e.currentTarget)
const responseMessage = await submit(formData)
const responseMessage = await submit(formData, undefined, mcp) // Pass mcp
setMessages(currentMessages => [...currentMessages, responseMessage as any])
}

Expand Down
7 changes: 4 additions & 3 deletions components/copilot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { Button } from './ui/button'
import { Card } from './ui/card'
import { ArrowRight, Check, FastForward, Sparkles } from 'lucide-react'
import { useActions, useStreamableValue, useUIState } from 'ai/rsc'
import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import
import type { AI } from '@/app/actions'
import {

import {


} from './ui/icons'
Expand All @@ -32,6 +32,7 @@ export const Copilot: React.FC<CopilotProps> = ({ inquiry }: CopilotProps) => {
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
const [, setMessages] = useUIState<typeof AI>()
const { submit } = useActions()
const mcp = useGeospatialToolMcp() // Call the hook

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value)
Expand Down Expand Up @@ -78,7 +79,7 @@ export const Copilot: React.FC<CopilotProps> = ({ inquiry }: CopilotProps) => {
? undefined
: new FormData(e.target as HTMLFormElement)

const response = await submit(formData, skip)
const response = await submit(formData, skip, mcp) // Pass mcp
setMessages(currentMessages => [...currentMessages, response])
}

Expand Down
4 changes: 3 additions & 1 deletion components/followup-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { useState } from 'react'
import { Button } from './ui/button'
import { Input } from './ui/input'
import { useActions, useUIState } from 'ai/rsc'
import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import
import type { AI } from '@/app/actions'
import { UserMessage } from './user-message'
import { ArrowRight } from 'lucide-react'

export function FollowupPanel() {
const [input, setInput] = useState('')
const { submit } = useActions()
const mcp = useGeospatialToolMcp() // Call the hook
const [, setMessages] = useUIState<typeof AI>()

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
Expand All @@ -23,7 +25,7 @@ export function FollowupPanel() {
component: <UserMessage message={input} />
}

const responseMessage = await submit(formData)
const responseMessage = await submit(formData, undefined, mcp) // Pass mcp
setMessages(currentMessages => [
...currentMessages,
userMessage,
Expand Down
4 changes: 3 additions & 1 deletion components/search-related.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
StreamableValue
} from 'ai/rsc'
import { AI } from '@/app/actions'
import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import
import { UserMessage } from './user-message'
import { PartialRelated } from '@/lib/schema/related'

Expand All @@ -22,6 +23,7 @@ export const SearchRelated: React.FC<SearchRelatedProps> = ({
relatedQueries
}) => {
const { submit } = useActions()
const mcp = useGeospatialToolMcp() // Call the hook
const [, setMessages] = useUIState<typeof AI>()
const [data, error, pending] =
useStreamableValue<PartialRelated>(relatedQueries)
Expand All @@ -44,7 +46,7 @@ export const SearchRelated: React.FC<SearchRelatedProps> = ({
component: <UserMessage message={query} />
}

const responseMessage = await submit(formData)
const responseMessage = await submit(formData, undefined, mcp) // Pass mcp
setMessages(currentMessages => [
...currentMessages,
userMessage,
Expand Down
4 changes: 3 additions & 1 deletion lib/agents/researcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export async function researcher(
uiStream: ReturnType<typeof createStreamableUI>,
streamText: ReturnType<typeof createStreamableValue<string>>,
messages: CoreMessage[],
mcp: any, // Moved mcp before optional parameter
useSpecificModel?: boolean
) {
let fullResponse = ''
Expand Down Expand Up @@ -54,7 +55,8 @@ Match the language of your response to the user's language.`;
messages,
tools: getTools({
uiStream,
fullResponse
fullResponse,
mcp // Pass the mcp parameter to getTools
})
})

Expand Down
115 changes: 82 additions & 33 deletions lib/agents/tools/geospatial.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { createStreamableValue } from 'ai/rsc';
import dotenv from 'dotenv';
dotenv.config();

import { useMcp } from 'use-mcp/react';
import { createStreamableUI, createStreamableValue } from 'ai/rsc';

import { BotMessage } from '@/components/message';
import { geospatialQuerySchema } from '@/lib/schema/geospatial';
import { ToolProps } from '.';

// Ensure dotenv is configured early in the application (if not already done in a parent module)
// This is typically done in the entry point (e.g., index.ts or app.ts), but included here for clarity
import dotenv from 'dotenv';
dotenv.config();
// Define proper MCP type
export type McpClient = ReturnType<typeof useMcp>;

// Move the MCP logic into a custom hook
export function useGeospatialToolMcp() {
const mcpServerUrl =
'https://server.smithery.ai/mapbox-mcp-server/mcp?api_key=705b0222-a657-4cd2-b180-80c406cf6179&profile=smooth-lemur-vfUbUE';
// Alternative: Dynamic URL (uncomment if using environment variables)
// const mcpServerUrl = `https://server.smithery.ai/@ngoiyaeric/mapbox-mcp-server/mcp?profile=${process.env.SMITHERY_PROFILE_ID}&api_key=${process.env.SMITHERY_API_KEY}`;

const mcp = useMcp({
url: mcpServerUrl,
debug: process.env.NODE_ENV === 'development',
Expand All @@ -26,7 +23,13 @@ export function useGeospatialToolMcp() {
return mcp;
}

export const geospatialTool = ({ uiStream, mcp }: ToolProps & { mcp: ReturnType<typeof useMcp> }) => ({
export const geospatialTool = ({
uiStream,
mcp,
}: {
uiStream: ReturnType<typeof createStreamableUI>;
mcp: McpClient | null;
}) => ({
description: `Use this tool for any queries that involve locations, places, addresses, distances between places, directions, or finding points of interest on a map. This includes questions like:
- 'Where is [place name/address]?'
- 'Show me [place name/address] on the map.'
Expand All @@ -43,8 +46,25 @@ export const geospatialTool = ({ uiStream, mcp }: ToolProps & { mcp: ReturnType<
uiFeedbackStream.done(`Looking up map information for: "${query}"...`);
uiStream.append(<BotMessage content={uiFeedbackStream.value} />);

// Check if MCP client is available
if (!mcp) {
console.warn('MCP client is not available, cannot proceed with geospatial query');
const errorStream = createStreamableValue<string>();
errorStream.done('Geospatial functionality is currently unavailable.');
uiStream.append(<BotMessage content={errorStream.value} />);
return {
type: 'MAP_QUERY_TRIGGER',
originalUserInput: query,
timestamp: new Date().toISOString(),
mcp_response: null,
error: 'MCP client not available',
};
}

// Log environment variables for debugging (with API key masked)
console.log(`[GeospatialTool] SMITHERY_PROFILE_ID: "${process.env.SMITHERY_PROFILE_ID ?? 'undefined'}"`);
console.log(
`[GeospatialTool] SMITHERY_PROFILE_ID: "${process.env.SMITHERY_PROFILE_ID ?? 'undefined'}"`
);
console.log(
`[GeospatialTool] SMITHERY_API_KEY: ${
process.env.SMITHERY_API_KEY
Expand All @@ -53,33 +73,54 @@ export const geospatialTool = ({ uiStream, mcp }: ToolProps & { mcp: ReturnType<
}`
);

let mcpData: {
location: {
latitude?: number;
longitude?: number;
place_name?: string;
address?: string;
};
mapUrl?: string;
} | null = null;
let mcpData:
| {
location: {
latitude?: number;
longitude?: number;
place_name?: string;
address?: string;
};
mapUrl?: string;
}
| null = null;

try {
console.log(`Attempting to connect to MCP server...`);

if (mcp.state !== 'ready') {
console.warn(`MCP client not ready (state: ${mcp.state}), cannot proceed with tool call.`);
throw new Error(`MCP client not ready (state: ${mcp.state})`);
if (!mcp || mcp.state !== 'ready') {
console.warn(`MCP client not ready${mcp ? ` (state: ${mcp.state})` : ''}, cannot proceed with tool call.`);
const errorStream = createStreamableValue<string>();
errorStream.done(`MCP client not ready${mcp ? ` (state: ${mcp.state})` : ''}. Please try again.`);
uiStream.append(<BotMessage content={errorStream.value} />);
return {
type: 'MAP_QUERY_TRIGGER',
originalUserInput: query,
timestamp: new Date().toISOString(),
mcp_response: null,
error: `MCP client not ready${mcp ? ` (state: ${mcp.state})` : ''}`,
};
}

console.log('✅ Successfully connected to MCP server.');

const geocodeParams = { query, includeMapPreview: true };
console.log('📞 Attempting to call "geocode_location" tool with params:', geocodeParams);
const geocodeResult = await mcp.callTool('geocode_location', geocodeParams);
console.log(
'📞 Attempting to call "geocode_location" tool with params:',
geocodeParams
);
const geocodeResult = await mcp.callTool(
'geocode_location',
geocodeParams
);

if (geocodeResult?.content && Array.isArray(geocodeResult.content)) {
const lastContentItem = geocodeResult.content[geocodeResult.content.length - 1];
if (lastContentItem?.type === 'text' && typeof lastContentItem.text === 'string') {
const lastContentItem =
geocodeResult.content[geocodeResult.content.length - 1];
if (
lastContentItem?.type === 'text' &&
typeof lastContentItem.text === 'string'
) {
const jsonRegex = /```json\n([\s\S]*?)\n```/;
const match = lastContentItem.text.match(jsonRegex);
if (match?.[1]) {
Expand All @@ -97,7 +138,9 @@ export const geospatialTool = ({ uiStream, mcp }: ToolProps & { mcp: ReturnType<
};
console.log('✅ Successfully parsed MCP geocode data:', mcpData);
} else {
console.warn("⚠️ Parsed JSON from MCP does not contain expected 'location' field.");
console.warn(
"⚠️ Parsed JSON from MCP does not contain expected 'location' field."
);
}
} catch (parseError) {
console.error(
Expand All @@ -108,10 +151,14 @@ export const geospatialTool = ({ uiStream, mcp }: ToolProps & { mcp: ReturnType<
);
}
} else {
console.warn('⚠️ Could not find JSON block in the expected format in MCP response.');
console.warn(
'⚠️ Could not find JSON block in the expected format in MCP response.'
);
}
} else {
console.warn('⚠️ Last content item from MCP is not a text block or is missing.');
console.warn(
'⚠️ Last content item from MCP is not a text block or is missing.'
);
}
} else {
console.warn(
Expand All @@ -122,8 +169,10 @@ export const geospatialTool = ({ uiStream, mcp }: ToolProps & { mcp: ReturnType<
} catch (error) {
console.error('❌ MCP connection or tool call failed:', error);
} finally {
if (mcp.state === 'ready') {
console.log('\nMCP client is ready; no explicit close method available for useMcp.');
if (mcp && mcp.state === 'ready') {
console.log(
'\n[GeospatialTool] MCP client is ready; no explicit close method available for useMcp.'
);
}
}

Expand Down
35 changes: 9 additions & 26 deletions lib/agents/tools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { createStreamableUI } from 'ai/rsc'
import { retrieveTool } from './retrieve'
import { searchTool } from './search'
import { videoSearchTool } from './video-search'
import { geospatialTool, useGeospatialToolMcp } from './geospatial'; // Added import
import { geospatialTool, useGeospatialToolMcp } from './geospatial'

export interface ToolProps {
uiStream: ReturnType<typeof createStreamableUI>
fullResponse: string
mcp?: ReturnType<typeof useGeospatialToolMcp>
}

export const getTools = ({ uiStream, fullResponse }: ToolProps) => {
export const getTools = ({ uiStream, fullResponse, mcp }: ToolProps) => {
const tools: any = {
search: searchTool({
uiStream,
Expand All @@ -18,13 +19,11 @@ export const getTools = ({ uiStream, fullResponse }: ToolProps) => {
retrieve: retrieveTool({
uiStream,
fullResponse
}),

geospatialQueryTool: geospatialTool({
uiStream,
fullResponse,
mcp: useGeospatialToolMcp()
})
}),
geospatialQueryTool: geospatialTool({
uiStream,
mcp: mcp || null
})
}

if (process.env.SERPER_API_KEY) {
Expand All @@ -35,20 +34,4 @@ export const getTools = ({ uiStream, fullResponse }: ToolProps) => {
}

return tools
}

export const useTools = ({ uiStream, fullResponse }: ToolProps) => {
const mcp = useGeospatialToolMcp();

const tools: any = {
search: searchTool({ uiStream, fullResponse }),
retrieve: retrieveTool({ uiStream, fullResponse }),
geospatialQueryTool: geospatialTool({ uiStream, fullResponse, mcp }),
};

if (process.env.SERPER_API_KEY) {
tools.videoSearch = videoSearchTool({ uiStream, fullResponse });
}

return tools;
};
}