Spaces:
Running
Running
Commit
·
2517ed6
1
Parent(s):
264f96c
feat: integrate MCP context for server management
Browse files- Introduced MCPProvider to manage MCP server state and selection using context.
- Updated Providers component to wrap children with MCPProvider.
- Refactored ChatSidebar and Chat components to utilize MCP context for server data.
- Removed redundant MCP server types and interfaces from local storage hooks.
- Enhanced overall structure for better state management and code clarity.
- app/api/chat/route.ts +4 -17
- app/favicon.ico +1 -1
- app/providers.tsx +7 -4
- components/chat-sidebar.tsx +5 -3
- components/chat.tsx +5 -39
- components/mcp-server-manager.tsx +1 -18
- components/model-picker.tsx +1 -1
- lib/context/mcp-context.tsx +99 -0
app/api/chat/route.ts
CHANGED
@@ -56,7 +56,7 @@ export async function POST(req: Request) {
|
|
56 |
|
57 |
// Check if chat already exists for the given ID
|
58 |
// If not, we'll create it in onFinish
|
59 |
-
|
60 |
if (chatId) {
|
61 |
try {
|
62 |
const existingChat = await db.query.chats.findFirst({
|
@@ -186,6 +186,8 @@ export async function POST(req: Request) {
|
|
186 |
You can run multiple steps using all the tools!!!!
|
187 |
Make sure to use the right tool to respond to the user's question.
|
188 |
|
|
|
|
|
189 |
## Response Format
|
190 |
- Markdown is supported.
|
191 |
- Respond according to tool's response.
|
@@ -198,33 +200,18 @@ export async function POST(req: Request) {
|
|
198 |
onError: (error) => {
|
199 |
console.error(JSON.stringify(error, null, 2));
|
200 |
},
|
201 |
-
async onFinish({ response
|
202 |
-
console.log("onFinish", response.messages.map(m => {
|
203 |
-
return {
|
204 |
-
id: m.id,
|
205 |
-
content: JSON.stringify(m.content),
|
206 |
-
role: m.role,
|
207 |
-
}
|
208 |
-
}));
|
209 |
-
console.log("steps", steps);
|
210 |
-
console.log("toolCalls", toolCalls);
|
211 |
-
console.log("toolResults", toolResults);
|
212 |
-
|
213 |
-
// Combine messages for processing
|
214 |
const allMessages = appendResponseMessages({
|
215 |
messages,
|
216 |
responseMessages: response.messages,
|
217 |
});
|
218 |
|
219 |
-
// Step 1: Save chat with messages for proper title generation
|
220 |
await saveChat({
|
221 |
id,
|
222 |
userId,
|
223 |
messages: allMessages,
|
224 |
-
// No title specified - will be generated
|
225 |
});
|
226 |
|
227 |
-
// Step 2: Save all messages
|
228 |
const dbMessages = convertToDBMessages(allMessages, id);
|
229 |
await saveMessages({ messages: dbMessages });
|
230 |
// close all mcp clients
|
|
|
56 |
|
57 |
// Check if chat already exists for the given ID
|
58 |
// If not, we'll create it in onFinish
|
59 |
+
var isNewChat = false;
|
60 |
if (chatId) {
|
61 |
try {
|
62 |
const existingChat = await db.query.chats.findFirst({
|
|
|
186 |
You can run multiple steps using all the tools!!!!
|
187 |
Make sure to use the right tool to respond to the user's question.
|
188 |
|
189 |
+
Multiple tools can be used in a single response and multiple steps can be used to answer the user's question.
|
190 |
+
|
191 |
## Response Format
|
192 |
- Markdown is supported.
|
193 |
- Respond according to tool's response.
|
|
|
200 |
onError: (error) => {
|
201 |
console.error(JSON.stringify(error, null, 2));
|
202 |
},
|
203 |
+
async onFinish({ response }) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
const allMessages = appendResponseMessages({
|
205 |
messages,
|
206 |
responseMessages: response.messages,
|
207 |
});
|
208 |
|
|
|
209 |
await saveChat({
|
210 |
id,
|
211 |
userId,
|
212 |
messages: allMessages,
|
|
|
213 |
});
|
214 |
|
|
|
215 |
const dbMessages = convertToDBMessages(allMessages, id);
|
216 |
await saveMessages({ messages: dbMessages });
|
217 |
// close all mcp clients
|
app/favicon.ico
CHANGED
|
Git LFS Details
|
|
Git LFS Details
|
app/providers.tsx
CHANGED
@@ -7,6 +7,7 @@ import { Toaster } from "sonner";
|
|
7 |
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
8 |
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
|
9 |
import { STORAGE_KEYS } from "@/lib/constants";
|
|
|
10 |
|
11 |
// Create a client
|
12 |
const queryClient = new QueryClient({
|
@@ -32,10 +33,12 @@ export function Providers({ children }: { children: ReactNode }) {
|
|
32 |
enableSystem
|
33 |
disableTransitionOnChange
|
34 |
>
|
35 |
-
<
|
36 |
-
{
|
37 |
-
|
38 |
-
|
|
|
|
|
39 |
</ThemeProvider>
|
40 |
</QueryClientProvider>
|
41 |
);
|
|
|
7 |
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
8 |
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
|
9 |
import { STORAGE_KEYS } from "@/lib/constants";
|
10 |
+
import { MCPProvider } from "@/lib/context/mcp-context";
|
11 |
|
12 |
// Create a client
|
13 |
const queryClient = new QueryClient({
|
|
|
33 |
enableSystem
|
34 |
disableTransitionOnChange
|
35 |
>
|
36 |
+
<MCPProvider>
|
37 |
+
<SidebarProvider defaultOpen={sidebarOpen} open={sidebarOpen} onOpenChange={setSidebarOpen}>
|
38 |
+
{children}
|
39 |
+
<Toaster position="top-center" richColors />
|
40 |
+
</SidebarProvider>
|
41 |
+
</MCPProvider>
|
42 |
</ThemeProvider>
|
43 |
</QueryClientProvider>
|
44 |
);
|
components/chat-sidebar.tsx
CHANGED
@@ -22,7 +22,7 @@ import { Button } from "@/components/ui/button";
|
|
22 |
import { Badge } from "@/components/ui/badge";
|
23 |
import { toast } from "sonner";
|
24 |
import Image from "next/image";
|
25 |
-
import { MCPServerManager
|
26 |
import { ThemeToggle } from "./theme-toggle";
|
27 |
import { getUserId, updateUserId } from "@/lib/user-id";
|
28 |
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
|
@@ -50,19 +50,21 @@ import {
|
|
50 |
} from "@/components/ui/dialog";
|
51 |
import { Input } from "@/components/ui/input";
|
52 |
import { Label } from "@/components/ui/label";
|
|
|
53 |
|
54 |
export function ChatSidebar() {
|
55 |
const router = useRouter();
|
56 |
const pathname = usePathname();
|
57 |
const [userId, setUserId] = useState<string>('');
|
58 |
-
const [mcpServers, setMcpServers] = useLocalStorage<MCPServer[]>(STORAGE_KEYS.MCP_SERVERS, []);
|
59 |
-
const [selectedMcpServers, setSelectedMcpServers] = useLocalStorage<string[]>(STORAGE_KEYS.SELECTED_MCP_SERVERS, []);
|
60 |
const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
|
61 |
const { state } = useSidebar();
|
62 |
const isCollapsed = state === "collapsed";
|
63 |
const [editUserIdOpen, setEditUserIdOpen] = useState(false);
|
64 |
const [newUserId, setNewUserId] = useState('');
|
65 |
|
|
|
|
|
|
|
66 |
// Initialize userId
|
67 |
useEffect(() => {
|
68 |
setUserId(getUserId());
|
|
|
22 |
import { Badge } from "@/components/ui/badge";
|
23 |
import { toast } from "sonner";
|
24 |
import Image from "next/image";
|
25 |
+
import { MCPServerManager } from "./mcp-server-manager";
|
26 |
import { ThemeToggle } from "./theme-toggle";
|
27 |
import { getUserId, updateUserId } from "@/lib/user-id";
|
28 |
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
|
|
|
50 |
} from "@/components/ui/dialog";
|
51 |
import { Input } from "@/components/ui/input";
|
52 |
import { Label } from "@/components/ui/label";
|
53 |
+
import { useMCP } from "@/lib/context/mcp-context";
|
54 |
|
55 |
export function ChatSidebar() {
|
56 |
const router = useRouter();
|
57 |
const pathname = usePathname();
|
58 |
const [userId, setUserId] = useState<string>('');
|
|
|
|
|
59 |
const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
|
60 |
const { state } = useSidebar();
|
61 |
const isCollapsed = state === "collapsed";
|
62 |
const [editUserIdOpen, setEditUserIdOpen] = useState(false);
|
63 |
const [newUserId, setNewUserId] = useState('');
|
64 |
|
65 |
+
// Get MCP server data from context
|
66 |
+
const { mcpServers, setMcpServers, selectedMcpServers, setSelectedMcpServers } = useMCP();
|
67 |
+
|
68 |
// Initialize userId
|
69 |
useEffect(() => {
|
70 |
setUserId(getUserId());
|
components/chat.tsx
CHANGED
@@ -9,31 +9,15 @@ import { Messages } from "./messages";
|
|
9 |
import { toast } from "sonner";
|
10 |
import { useRouter, useParams } from "next/navigation";
|
11 |
import { getUserId } from "@/lib/user-id";
|
12 |
-
import {
|
13 |
import { STORAGE_KEYS } from "@/lib/constants";
|
14 |
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
15 |
import { convertToUIMessages } from "@/lib/chat-store";
|
16 |
import { type Message as DBMessage } from "@/lib/db/schema";
|
17 |
import { nanoid } from "nanoid";
|
|
|
18 |
|
19 |
-
//
|
20 |
-
interface KeyValuePair {
|
21 |
-
key: string;
|
22 |
-
value: string;
|
23 |
-
}
|
24 |
-
|
25 |
-
interface MCPServer {
|
26 |
-
id: string;
|
27 |
-
name: string;
|
28 |
-
url: string;
|
29 |
-
type: 'sse' | 'stdio';
|
30 |
-
command?: string;
|
31 |
-
args?: string[];
|
32 |
-
env?: KeyValuePair[];
|
33 |
-
headers?: KeyValuePair[];
|
34 |
-
}
|
35 |
-
|
36 |
-
|
37 |
interface ChatData {
|
38 |
id: string;
|
39 |
messages: DBMessage[];
|
@@ -51,9 +35,8 @@ export default function Chat() {
|
|
51 |
const [userId, setUserId] = useState<string>('');
|
52 |
const [generatedChatId, setGeneratedChatId] = useState<string>('');
|
53 |
|
54 |
-
// Get MCP server data from
|
55 |
-
const
|
56 |
-
const selectedMcpServers = useLocalStorageValue<string[]>(STORAGE_KEYS.SELECTED_MCP_SERVERS, []);
|
57 |
|
58 |
// Initialize userId
|
59 |
useEffect(() => {
|
@@ -98,23 +81,6 @@ export default function Chat() {
|
|
98 |
staleTime: 1000 * 60 * 5, // 5 minutes
|
99 |
refetchOnWindowFocus: false
|
100 |
});
|
101 |
-
|
102 |
-
// Memoize MCP server configuration for API
|
103 |
-
const mcpServersForApi = useMemo(() => {
|
104 |
-
if (!selectedMcpServers.length) return [];
|
105 |
-
|
106 |
-
return selectedMcpServers
|
107 |
-
.map(id => mcpServers.find(server => server.id === id))
|
108 |
-
.filter((server): server is MCPServer => Boolean(server))
|
109 |
-
.map(server => ({
|
110 |
-
type: server.type,
|
111 |
-
url: server.url,
|
112 |
-
command: server.command,
|
113 |
-
args: server.args,
|
114 |
-
env: server.env,
|
115 |
-
headers: server.headers
|
116 |
-
}));
|
117 |
-
}, [mcpServers, selectedMcpServers]);
|
118 |
|
119 |
// Prepare initial messages from query data
|
120 |
const initialMessages = useMemo(() => {
|
|
|
9 |
import { toast } from "sonner";
|
10 |
import { useRouter, useParams } from "next/navigation";
|
11 |
import { getUserId } from "@/lib/user-id";
|
12 |
+
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
|
13 |
import { STORAGE_KEYS } from "@/lib/constants";
|
14 |
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
15 |
import { convertToUIMessages } from "@/lib/chat-store";
|
16 |
import { type Message as DBMessage } from "@/lib/db/schema";
|
17 |
import { nanoid } from "nanoid";
|
18 |
+
import { useMCP } from "@/lib/context/mcp-context";
|
19 |
|
20 |
+
// Type for chat data from DB
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
interface ChatData {
|
22 |
id: string;
|
23 |
messages: DBMessage[];
|
|
|
35 |
const [userId, setUserId] = useState<string>('');
|
36 |
const [generatedChatId, setGeneratedChatId] = useState<string>('');
|
37 |
|
38 |
+
// Get MCP server data from context
|
39 |
+
const { mcpServersForApi } = useMCP();
|
|
|
40 |
|
41 |
// Initialize userId
|
42 |
useEffect(() => {
|
|
|
81 |
staleTime: 1000 * 60 * 5, // 5 minutes
|
82 |
refetchOnWindowFocus: false
|
83 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
85 |
// Prepare initial messages from query data
|
86 |
const initialMessages = useMemo(() => {
|
components/mcp-server-manager.tsx
CHANGED
@@ -40,12 +40,7 @@ import {
|
|
40 |
AccordionItem,
|
41 |
AccordionTrigger
|
42 |
} from "./ui/accordion";
|
43 |
-
|
44 |
-
// Key-value pair for environment variables and headers
|
45 |
-
interface KeyValuePair {
|
46 |
-
key: string;
|
47 |
-
value: string;
|
48 |
-
}
|
49 |
|
50 |
// Default template for a new MCP server
|
51 |
const INITIAL_NEW_SERVER: Omit<MCPServer, 'id'> = {
|
@@ -58,18 +53,6 @@ const INITIAL_NEW_SERVER: Omit<MCPServer, 'id'> = {
|
|
58 |
headers: []
|
59 |
};
|
60 |
|
61 |
-
export interface MCPServer {
|
62 |
-
id: string;
|
63 |
-
name: string;
|
64 |
-
url: string;
|
65 |
-
type: 'sse' | 'stdio';
|
66 |
-
command?: string;
|
67 |
-
args?: string[];
|
68 |
-
env?: KeyValuePair[];
|
69 |
-
headers?: KeyValuePair[];
|
70 |
-
description?: string;
|
71 |
-
}
|
72 |
-
|
73 |
interface MCPServerManagerProps {
|
74 |
servers: MCPServer[];
|
75 |
onServersChange: (servers: MCPServer[]) => void;
|
|
|
40 |
AccordionItem,
|
41 |
AccordionTrigger
|
42 |
} from "./ui/accordion";
|
43 |
+
import { KeyValuePair, MCPServer } from "@/lib/context/mcp-context";
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
// Default template for a new MCP server
|
46 |
const INITIAL_NEW_SERVER: Omit<MCPServer, 'id'> = {
|
|
|
53 |
headers: []
|
54 |
};
|
55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
interface MCPServerManagerProps {
|
57 |
servers: MCPServer[];
|
58 |
onServersChange: (servers: MCPServer[]) => void;
|
components/model-picker.tsx
CHANGED
@@ -168,7 +168,7 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
|
|
168 |
</div>
|
169 |
|
170 |
{/* Model details column - hidden on smallest screens, visible on sm+ */}
|
171 |
-
<div className="p-2 sm:p-3 md:p-4 flex
|
172 |
<div>
|
173 |
<div className="flex items-center gap-2 mb-1">
|
174 |
{getProviderIcon(currentModelDetails.provider)}
|
|
|
168 |
</div>
|
169 |
|
170 |
{/* Model details column - hidden on smallest screens, visible on sm+ */}
|
171 |
+
<div className="sm:block hidden p-2 sm:p-3 md:p-4 flex-col">
|
172 |
<div>
|
173 |
<div className="flex items-center gap-2 mb-1">
|
174 |
{getProviderIcon(currentModelDetails.provider)}
|
lib/context/mcp-context.tsx
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import React, { createContext, useContext, useEffect, useState } from "react";
|
4 |
+
import { useLocalStorage } from "@/lib/hooks/use-local-storage";
|
5 |
+
import { STORAGE_KEYS } from "@/lib/constants";
|
6 |
+
|
7 |
+
// Define types for MCP server
|
8 |
+
export interface KeyValuePair {
|
9 |
+
key: string;
|
10 |
+
value: string;
|
11 |
+
}
|
12 |
+
|
13 |
+
export interface MCPServer {
|
14 |
+
id: string;
|
15 |
+
name: string;
|
16 |
+
url: string;
|
17 |
+
type: 'sse' | 'stdio';
|
18 |
+
command?: string;
|
19 |
+
args?: string[];
|
20 |
+
env?: KeyValuePair[];
|
21 |
+
headers?: KeyValuePair[];
|
22 |
+
description?: string;
|
23 |
+
}
|
24 |
+
|
25 |
+
// Type for processed MCP server config for API
|
26 |
+
export interface MCPServerApi {
|
27 |
+
type: 'sse' | 'stdio';
|
28 |
+
url: string;
|
29 |
+
command?: string;
|
30 |
+
args?: string[];
|
31 |
+
env?: KeyValuePair[];
|
32 |
+
headers?: KeyValuePair[];
|
33 |
+
}
|
34 |
+
|
35 |
+
interface MCPContextType {
|
36 |
+
mcpServers: MCPServer[];
|
37 |
+
setMcpServers: (servers: MCPServer[]) => void;
|
38 |
+
selectedMcpServers: string[];
|
39 |
+
setSelectedMcpServers: (serverIds: string[]) => void;
|
40 |
+
mcpServersForApi: MCPServerApi[];
|
41 |
+
}
|
42 |
+
|
43 |
+
const MCPContext = createContext<MCPContextType | undefined>(undefined);
|
44 |
+
|
45 |
+
export function MCPProvider({ children }: { children: React.ReactNode }) {
|
46 |
+
const [mcpServers, setMcpServers] = useLocalStorage<MCPServer[]>(
|
47 |
+
STORAGE_KEYS.MCP_SERVERS,
|
48 |
+
[]
|
49 |
+
);
|
50 |
+
const [selectedMcpServers, setSelectedMcpServers] = useLocalStorage<string[]>(
|
51 |
+
STORAGE_KEYS.SELECTED_MCP_SERVERS,
|
52 |
+
[]
|
53 |
+
);
|
54 |
+
const [mcpServersForApi, setMcpServersForApi] = useState<MCPServerApi[]>([]);
|
55 |
+
|
56 |
+
// Process MCP servers for API consumption whenever server data changes
|
57 |
+
useEffect(() => {
|
58 |
+
if (!selectedMcpServers.length) {
|
59 |
+
setMcpServersForApi([]);
|
60 |
+
return;
|
61 |
+
}
|
62 |
+
|
63 |
+
const processedServers: MCPServerApi[] = selectedMcpServers
|
64 |
+
.map(id => mcpServers.find(server => server.id === id))
|
65 |
+
.filter((server): server is MCPServer => Boolean(server))
|
66 |
+
.map(server => ({
|
67 |
+
type: server.type,
|
68 |
+
url: server.url,
|
69 |
+
command: server.command,
|
70 |
+
args: server.args,
|
71 |
+
env: server.env,
|
72 |
+
headers: server.headers
|
73 |
+
}));
|
74 |
+
|
75 |
+
setMcpServersForApi(processedServers);
|
76 |
+
}, [mcpServers, selectedMcpServers]);
|
77 |
+
|
78 |
+
return (
|
79 |
+
<MCPContext.Provider
|
80 |
+
value={{
|
81 |
+
mcpServers,
|
82 |
+
setMcpServers,
|
83 |
+
selectedMcpServers,
|
84 |
+
setSelectedMcpServers,
|
85 |
+
mcpServersForApi
|
86 |
+
}}
|
87 |
+
>
|
88 |
+
{children}
|
89 |
+
</MCPContext.Provider>
|
90 |
+
);
|
91 |
+
}
|
92 |
+
|
93 |
+
export function useMCP() {
|
94 |
+
const context = useContext(MCPContext);
|
95 |
+
if (context === undefined) {
|
96 |
+
throw new Error("useMCP must be used within an MCPProvider");
|
97 |
+
}
|
98 |
+
return context;
|
99 |
+
}
|