mukaddamzaid commited on
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 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
- let isNewChat = false;
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, steps, toolCalls, toolResults }) {
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

  • SHA256: fae26f65fd15655032d2a57c8e05d4afb9d3119e9a96759b9240acdbe3bb8026
  • Pointer size: 130 Bytes
  • Size of remote file: 15.4 kB

Git LFS Details

  • SHA256: f66f2d70cd0ae9e2583a45c63504adcd6401d0d78309a7a855a06c0b07253f05
  • Pointer size: 130 Bytes
  • Size of remote file: 15.4 kB
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
- <SidebarProvider defaultOpen={sidebarOpen} open={sidebarOpen} onOpenChange={setSidebarOpen}>
36
- {children}
37
- <Toaster position="top-center" richColors />
38
- </SidebarProvider>
 
 
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, type MCPServer } 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,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 { useLocalStorageValue, 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
 
19
- // Define types for MCP server
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 localStorage via our custom hooks
55
- const mcpServers = useLocalStorageValue<MCPServer[]>(STORAGE_KEYS.MCP_SERVERS, []);
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 flex-col sm:block hidden">
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
+ }