Spaces:
Running
Running
feat: add conversation management functionality and update MCPClientService to handle resources and prompts
Browse files- package.json +1 -1
- src/App.tsx +195 -14
- src/constants/db.ts +1 -0
- src/services/mcpClient.ts +55 -1
- src/types/mcp.ts +7 -1
package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
| 11 |
},
|
| 12 |
"dependencies": {
|
| 13 |
"@huggingface/transformers": "^3.7.5",
|
| 14 |
-
"@modelcontextprotocol/sdk": "^1.
|
| 15 |
"@monaco-editor/react": "^4.7.0",
|
| 16 |
"@tailwindcss/vite": "^4.1.11",
|
| 17 |
"dompurify": "^3.2.7",
|
|
|
|
| 11 |
},
|
| 12 |
"dependencies": {
|
| 13 |
"@huggingface/transformers": "^3.7.5",
|
| 14 |
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
| 15 |
"@monaco-editor/react": "^4.7.0",
|
| 16 |
"@tailwindcss/vite": "^4.1.11",
|
| 17 |
"dompurify": "^3.2.7",
|
src/App.tsx
CHANGED
|
@@ -35,7 +35,8 @@ import {
|
|
| 35 |
} from "./utils";
|
| 36 |
|
| 37 |
import { DEFAULT_SYSTEM_PROMPT } from "./constants/systemPrompt";
|
| 38 |
-
|
|
|
|
| 39 |
|
| 40 |
import { TEMPLATE } from "./tools";
|
| 41 |
import ToolResultRenderer from "./components/ToolResultRenderer";
|
|
@@ -66,22 +67,81 @@ interface ToolMessage {
|
|
| 66 |
}
|
| 67 |
type Message = BaseMessage | ToolMessage;
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
async function getDB(): Promise<IDBPDatabase> {
|
| 70 |
-
return openDB(DB_NAME,
|
| 71 |
-
upgrade(db) {
|
| 72 |
-
if (
|
| 73 |
-
db.
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
| 78 |
-
if (
|
| 79 |
-
db.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
},
|
| 82 |
});
|
| 83 |
}
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
function renderMarkdown(text: string): string {
|
| 86 |
return DOMPurify.sanitize(marked.parse(text) as string);
|
| 87 |
}
|
|
@@ -105,6 +165,9 @@ const App: React.FC = () => {
|
|
| 105 |
useState<boolean>(false);
|
| 106 |
const [isMCPManagerOpen, setIsMCPManagerOpen] = useState<boolean>(false);
|
| 107 |
const [isToolsPanelVisible, setIsToolsPanelVisible] = useState<boolean>(false);
|
|
|
|
|
|
|
|
|
|
| 108 |
const chatContainerRef = useRef<HTMLDivElement>(null);
|
| 109 |
const debounceTimers = useRef<Record<number, NodeJS.Timeout>>({});
|
| 110 |
const toolsContainerRef = useRef<HTMLDivElement>(null);
|
|
@@ -207,6 +270,42 @@ const App: React.FC = () => {
|
|
| 207 |
}
|
| 208 |
}, [messages]);
|
| 209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
const updateToolInDB = async (tool: Tool): Promise<void> => {
|
| 211 |
const db = await getDB();
|
| 212 |
await db.put(STORE_NAME, tool);
|
|
@@ -228,6 +327,34 @@ const App: React.FC = () => {
|
|
| 228 |
clearPastKeyValues();
|
| 229 |
}, [clearPastKeyValues]);
|
| 230 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
const addTool = async (): Promise<void> => {
|
| 232 |
const newTool: Omit<Tool, "id"> = {
|
| 233 |
name: "new_tool",
|
|
@@ -698,9 +825,52 @@ const App: React.FC = () => {
|
|
| 698 |
/>
|
| 699 |
) : (
|
| 700 |
<div className="flex h-screen text-white">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
<div
|
| 702 |
className={`flex flex-col p-4 transition-all duration-300 ${
|
| 703 |
-
isToolsPanelVisible ? "w-1/2" : "w-full"
|
| 704 |
}`}
|
| 705 |
>
|
| 706 |
<div className="flex items-center justify-between mb-4">
|
|
@@ -712,17 +882,28 @@ const App: React.FC = () => {
|
|
| 712 |
<Zap size={16} className="mr-2" />
|
| 713 |
Ready
|
| 714 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 715 |
<button
|
| 716 |
disabled={isGenerating}
|
| 717 |
-
onClick={
|
| 718 |
className={`h-10 flex items-center px-3 py-2 rounded-lg font-bold transition-colors text-sm ${
|
| 719 |
isGenerating
|
| 720 |
? "bg-gray-600 cursor-not-allowed opacity-50"
|
| 721 |
: "bg-gray-600 hover:bg-gray-700"
|
| 722 |
}`}
|
| 723 |
-
title="
|
| 724 |
>
|
| 725 |
-
<
|
| 726 |
</button>
|
| 727 |
<button
|
| 728 |
onClick={handleOpenSystemPromptModal}
|
|
|
|
| 35 |
} from "./utils";
|
| 36 |
|
| 37 |
import { DEFAULT_SYSTEM_PROMPT } from "./constants/systemPrompt";
|
| 38 |
+
|
| 39 |
+
import { DB_NAME, STORE_NAME, SETTINGS_STORE_NAME, CONVERSATIONS_STORE_NAME } from "./constants/db";
|
| 40 |
|
| 41 |
import { TEMPLATE } from "./tools";
|
| 42 |
import ToolResultRenderer from "./components/ToolResultRenderer";
|
|
|
|
| 67 |
}
|
| 68 |
type Message = BaseMessage | ToolMessage;
|
| 69 |
|
| 70 |
+
interface Conversation {
|
| 71 |
+
id?: number;
|
| 72 |
+
title: string;
|
| 73 |
+
messages: Message[];
|
| 74 |
+
createdAt: number;
|
| 75 |
+
updatedAt: number;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
async function getDB(): Promise<IDBPDatabase> {
|
| 79 |
+
return openDB(DB_NAME, 2, {
|
| 80 |
+
upgrade(db, oldVersion) {
|
| 81 |
+
if (oldVersion < 1) {
|
| 82 |
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
| 83 |
+
db.createObjectStore(STORE_NAME, {
|
| 84 |
+
keyPath: "id",
|
| 85 |
+
autoIncrement: true,
|
| 86 |
+
});
|
| 87 |
+
}
|
| 88 |
+
if (!db.objectStoreNames.contains(SETTINGS_STORE_NAME)) {
|
| 89 |
+
db.createObjectStore(SETTINGS_STORE_NAME, { keyPath: "key" });
|
| 90 |
+
}
|
| 91 |
}
|
| 92 |
+
if (oldVersion < 2) {
|
| 93 |
+
if (!db.objectStoreNames.contains(CONVERSATIONS_STORE_NAME)) {
|
| 94 |
+
const conversationStore = db.createObjectStore(CONVERSATIONS_STORE_NAME, {
|
| 95 |
+
keyPath: "id",
|
| 96 |
+
autoIncrement: true,
|
| 97 |
+
});
|
| 98 |
+
conversationStore.createIndex("updatedAt", "updatedAt");
|
| 99 |
+
}
|
| 100 |
}
|
| 101 |
},
|
| 102 |
});
|
| 103 |
}
|
| 104 |
|
| 105 |
+
// Conversation management functions
|
| 106 |
+
async function saveConversation(conversation: Conversation): Promise<number> {
|
| 107 |
+
const db = await getDB();
|
| 108 |
+
const id = await db.put(CONVERSATIONS_STORE_NAME, {
|
| 109 |
+
...conversation,
|
| 110 |
+
updatedAt: Date.now(),
|
| 111 |
+
});
|
| 112 |
+
return id as number;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
async function loadConversation(id: number): Promise<Conversation | undefined> {
|
| 116 |
+
const db = await getDB();
|
| 117 |
+
return db.get(CONVERSATIONS_STORE_NAME, id);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
async function loadAllConversations(): Promise<Conversation[]> {
|
| 121 |
+
const db = await getDB();
|
| 122 |
+
const conversations = await db.getAllFromIndex(
|
| 123 |
+
CONVERSATIONS_STORE_NAME,
|
| 124 |
+
"updatedAt"
|
| 125 |
+
);
|
| 126 |
+
return conversations.reverse(); // Most recent first
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async function deleteConversation(id: number): Promise<void> {
|
| 130 |
+
const db = await getDB();
|
| 131 |
+
await db.delete(CONVERSATIONS_STORE_NAME, id);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
function generateConversationTitle(messages: Message[]): string {
|
| 135 |
+
const firstUserMessage = messages.find((m) => m.role === "user");
|
| 136 |
+
if (firstUserMessage) {
|
| 137 |
+
const content = firstUserMessage.content.substring(0, 50);
|
| 138 |
+
return content.length < firstUserMessage.content.length
|
| 139 |
+
? content + "..."
|
| 140 |
+
: content;
|
| 141 |
+
}
|
| 142 |
+
return "New Conversation";
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
function renderMarkdown(text: string): string {
|
| 146 |
return DOMPurify.sanitize(marked.parse(text) as string);
|
| 147 |
}
|
|
|
|
| 165 |
useState<boolean>(false);
|
| 166 |
const [isMCPManagerOpen, setIsMCPManagerOpen] = useState<boolean>(false);
|
| 167 |
const [isToolsPanelVisible, setIsToolsPanelVisible] = useState<boolean>(false);
|
| 168 |
+
const [currentConversationId, setCurrentConversationId] = useState<number | null>(null);
|
| 169 |
+
const [conversations, setConversations] = useState<Conversation[]>([]);
|
| 170 |
+
const [isConversationsPanelVisible, setIsConversationsPanelVisible] = useState<boolean>(false);
|
| 171 |
const chatContainerRef = useRef<HTMLDivElement>(null);
|
| 172 |
const debounceTimers = useRef<Record<number, NodeJS.Timeout>>({});
|
| 173 |
const toolsContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
| 270 |
}
|
| 271 |
}, [messages]);
|
| 272 |
|
| 273 |
+
// Load all conversations on mount
|
| 274 |
+
useEffect(() => {
|
| 275 |
+
loadAllConversations().then(setConversations).catch((error) => {
|
| 276 |
+
console.error("Failed to load conversations:", error);
|
| 277 |
+
});
|
| 278 |
+
}, []);
|
| 279 |
+
|
| 280 |
+
// Auto-save current conversation when messages change
|
| 281 |
+
useEffect(() => {
|
| 282 |
+
if (messages.length === 0) return;
|
| 283 |
+
|
| 284 |
+
const saveCurrentConversation = async () => {
|
| 285 |
+
const title = generateConversationTitle(messages);
|
| 286 |
+
const conversation: Conversation = {
|
| 287 |
+
...(currentConversationId ? { id: currentConversationId } : {}),
|
| 288 |
+
title,
|
| 289 |
+
messages,
|
| 290 |
+
createdAt: currentConversationId ? conversations.find(c => c.id === currentConversationId)?.createdAt || Date.now() : Date.now(),
|
| 291 |
+
updatedAt: Date.now(),
|
| 292 |
+
};
|
| 293 |
+
|
| 294 |
+
const id = await saveConversation(conversation);
|
| 295 |
+
if (!currentConversationId) {
|
| 296 |
+
setCurrentConversationId(id);
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
// Reload conversations list
|
| 300 |
+
const updatedConversations = await loadAllConversations();
|
| 301 |
+
setConversations(updatedConversations);
|
| 302 |
+
};
|
| 303 |
+
|
| 304 |
+
saveCurrentConversation().catch((error) => {
|
| 305 |
+
console.error("Failed to save conversation:", error);
|
| 306 |
+
});
|
| 307 |
+
}, [messages, currentConversationId, conversations]);
|
| 308 |
+
|
| 309 |
const updateToolInDB = async (tool: Tool): Promise<void> => {
|
| 310 |
const db = await getDB();
|
| 311 |
await db.put(STORE_NAME, tool);
|
|
|
|
| 327 |
clearPastKeyValues();
|
| 328 |
}, [clearPastKeyValues]);
|
| 329 |
|
| 330 |
+
// Conversation management handlers
|
| 331 |
+
const handleNewConversation = useCallback(() => {
|
| 332 |
+
setMessages([]);
|
| 333 |
+
setCurrentConversationId(null);
|
| 334 |
+
clearPastKeyValues();
|
| 335 |
+
}, [clearPastKeyValues]);
|
| 336 |
+
|
| 337 |
+
const handleLoadConversation = useCallback(async (id: number) => {
|
| 338 |
+
const conversation = await loadConversation(id);
|
| 339 |
+
if (conversation) {
|
| 340 |
+
setMessages(conversation.messages);
|
| 341 |
+
setCurrentConversationId(id);
|
| 342 |
+
clearPastKeyValues();
|
| 343 |
+
setIsConversationsPanelVisible(false);
|
| 344 |
+
}
|
| 345 |
+
}, [clearPastKeyValues]);
|
| 346 |
+
|
| 347 |
+
const handleDeleteConversation = useCallback(async (id: number) => {
|
| 348 |
+
await deleteConversation(id);
|
| 349 |
+
const updatedConversations = await loadAllConversations();
|
| 350 |
+
setConversations(updatedConversations);
|
| 351 |
+
|
| 352 |
+
// If deleting current conversation, start a new one
|
| 353 |
+
if (id === currentConversationId) {
|
| 354 |
+
handleNewConversation();
|
| 355 |
+
}
|
| 356 |
+
}, [currentConversationId, handleNewConversation]);
|
| 357 |
+
|
| 358 |
const addTool = async (): Promise<void> => {
|
| 359 |
const newTool: Omit<Tool, "id"> = {
|
| 360 |
name: "new_tool",
|
|
|
|
| 825 |
/>
|
| 826 |
) : (
|
| 827 |
<div className="flex h-screen text-white">
|
| 828 |
+
{isConversationsPanelVisible && (
|
| 829 |
+
<div className="w-80 flex flex-col p-4 border-r border-gray-700 bg-gray-900">
|
| 830 |
+
<h2 className="text-2xl font-bold text-indigo-400 mb-4">Conversations</h2>
|
| 831 |
+
<div className="flex-grow overflow-y-auto space-y-2">
|
| 832 |
+
{conversations.length === 0 ? (
|
| 833 |
+
<p className="text-gray-400 text-sm text-center mt-8">
|
| 834 |
+
No saved conversations yet
|
| 835 |
+
</p>
|
| 836 |
+
) : (
|
| 837 |
+
conversations.map((conv) => (
|
| 838 |
+
<div
|
| 839 |
+
key={conv.id}
|
| 840 |
+
className={`p-3 rounded-lg cursor-pointer transition-colors ${
|
| 841 |
+
conv.id === currentConversationId
|
| 842 |
+
? "bg-indigo-700"
|
| 843 |
+
: "bg-gray-800 hover:bg-gray-700"
|
| 844 |
+
}`}
|
| 845 |
+
onClick={() => handleLoadConversation(conv.id!)}
|
| 846 |
+
>
|
| 847 |
+
<div className="flex justify-between items-start mb-1">
|
| 848 |
+
<h3 className="text-sm font-semibold text-white truncate flex-1">
|
| 849 |
+
{conv.title}
|
| 850 |
+
</h3>
|
| 851 |
+
<button
|
| 852 |
+
onClick={(e) => {
|
| 853 |
+
e.stopPropagation();
|
| 854 |
+
handleDeleteConversation(conv.id!);
|
| 855 |
+
}}
|
| 856 |
+
className="text-gray-400 hover:text-red-400 ml-2"
|
| 857 |
+
title="Delete conversation"
|
| 858 |
+
>
|
| 859 |
+
<X size={14} />
|
| 860 |
+
</button>
|
| 861 |
+
</div>
|
| 862 |
+
<p className="text-xs text-gray-400">
|
| 863 |
+
{new Date(conv.updatedAt).toLocaleDateString()}
|
| 864 |
+
</p>
|
| 865 |
+
</div>
|
| 866 |
+
))
|
| 867 |
+
)}
|
| 868 |
+
</div>
|
| 869 |
+
</div>
|
| 870 |
+
)}
|
| 871 |
<div
|
| 872 |
className={`flex flex-col p-4 transition-all duration-300 ${
|
| 873 |
+
isToolsPanelVisible ? "w-1/2" : isConversationsPanelVisible ? "flex-1" : "w-full"
|
| 874 |
}`}
|
| 875 |
>
|
| 876 |
<div className="flex items-center justify-between mb-4">
|
|
|
|
| 882 |
<Zap size={16} className="mr-2" />
|
| 883 |
Ready
|
| 884 |
</div>
|
| 885 |
+
<button
|
| 886 |
+
onClick={() => setIsConversationsPanelVisible(!isConversationsPanelVisible)}
|
| 887 |
+
className="h-10 flex items-center px-3 py-2 rounded-lg font-bold transition-colors bg-gray-600 hover:bg-gray-700 text-sm"
|
| 888 |
+
title={
|
| 889 |
+
isConversationsPanelVisible
|
| 890 |
+
? "Hide Conversations"
|
| 891 |
+
: "Show Conversations"
|
| 892 |
+
}
|
| 893 |
+
>
|
| 894 |
+
📝
|
| 895 |
+
</button>
|
| 896 |
<button
|
| 897 |
disabled={isGenerating}
|
| 898 |
+
onClick={handleNewConversation}
|
| 899 |
className={`h-10 flex items-center px-3 py-2 rounded-lg font-bold transition-colors text-sm ${
|
| 900 |
isGenerating
|
| 901 |
? "bg-gray-600 cursor-not-allowed opacity-50"
|
| 902 |
: "bg-gray-600 hover:bg-gray-700"
|
| 903 |
}`}
|
| 904 |
+
title="New conversation"
|
| 905 |
>
|
| 906 |
+
<Plus size={14} className="mr-2" /> New
|
| 907 |
</button>
|
| 908 |
<button
|
| 909 |
onClick={handleOpenSystemPromptModal}
|
src/constants/db.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
export const DB_NAME = "tool-caller-db";
|
| 2 |
export const STORE_NAME = "tools";
|
| 3 |
export const SETTINGS_STORE_NAME = "settings";
|
|
|
|
|
|
| 1 |
export const DB_NAME = "tool-caller-db";
|
| 2 |
export const STORE_NAME = "tools";
|
| 3 |
export const SETTINGS_STORE_NAME = "settings";
|
| 4 |
+
export const CONVERSATIONS_STORE_NAME = "conversations";
|
src/services/mcpClient.ts
CHANGED
|
@@ -72,6 +72,8 @@ export class MCPClientService {
|
|
| 72 |
config,
|
| 73 |
isConnected: false,
|
| 74 |
tools: [],
|
|
|
|
|
|
|
| 75 |
lastError: undefined,
|
| 76 |
lastConnected: undefined,
|
| 77 |
};
|
|
@@ -107,6 +109,8 @@ export class MCPClientService {
|
|
| 107 |
config,
|
| 108 |
isConnected: false,
|
| 109 |
tools: [],
|
|
|
|
|
|
|
| 110 |
lastError: undefined,
|
| 111 |
lastConnected: undefined,
|
| 112 |
};
|
|
@@ -155,6 +159,8 @@ export class MCPClientService {
|
|
| 155 |
{
|
| 156 |
capabilities: {
|
| 157 |
tools: {},
|
|
|
|
|
|
|
| 158 |
},
|
| 159 |
}
|
| 160 |
);
|
|
@@ -226,12 +232,34 @@ export class MCPClientService {
|
|
| 226 |
// Connect to the server
|
| 227 |
await client.connect(transport);
|
| 228 |
|
| 229 |
-
// List available tools
|
| 230 |
const toolsResult = await client.listTools();
|
| 231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
// Update connection state
|
| 233 |
connection.isConnected = true;
|
| 234 |
connection.tools = toolsResult.tools;
|
|
|
|
|
|
|
| 235 |
connection.lastError = undefined;
|
| 236 |
connection.lastConnected = new Date();
|
| 237 |
|
|
@@ -283,6 +311,32 @@ export class MCPClientService {
|
|
| 283 |
return allTools;
|
| 284 |
}
|
| 285 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
// Call a tool on an MCP server
|
| 287 |
async callTool(
|
| 288 |
serverId: string,
|
|
|
|
| 72 |
config,
|
| 73 |
isConnected: false,
|
| 74 |
tools: [],
|
| 75 |
+
resources: [],
|
| 76 |
+
prompts: [],
|
| 77 |
lastError: undefined,
|
| 78 |
lastConnected: undefined,
|
| 79 |
};
|
|
|
|
| 109 |
config,
|
| 110 |
isConnected: false,
|
| 111 |
tools: [],
|
| 112 |
+
resources: [],
|
| 113 |
+
prompts: [],
|
| 114 |
lastError: undefined,
|
| 115 |
lastConnected: undefined,
|
| 116 |
};
|
|
|
|
| 159 |
{
|
| 160 |
capabilities: {
|
| 161 |
tools: {},
|
| 162 |
+
resources: {},
|
| 163 |
+
prompts: {},
|
| 164 |
},
|
| 165 |
}
|
| 166 |
);
|
|
|
|
| 232 |
// Connect to the server
|
| 233 |
await client.connect(transport);
|
| 234 |
|
| 235 |
+
// List available tools (always required)
|
| 236 |
const toolsResult = await client.listTools();
|
| 237 |
|
| 238 |
+
// List resources if supported (optional - not all servers implement this)
|
| 239 |
+
let resources: any[] = [];
|
| 240 |
+
try {
|
| 241 |
+
const resourcesResult = await client.listResources();
|
| 242 |
+
resources = resourcesResult.resources;
|
| 243 |
+
} catch (error) {
|
| 244 |
+
// Server doesn't support resources - that's okay
|
| 245 |
+
console.log(`Server ${serverId} does not support resources:`, error instanceof Error ? error.message : String(error));
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
// List prompts if supported (optional - not all servers implement this)
|
| 249 |
+
let prompts: any[] = [];
|
| 250 |
+
try {
|
| 251 |
+
const promptsResult = await client.listPrompts();
|
| 252 |
+
prompts = promptsResult.prompts;
|
| 253 |
+
} catch (error) {
|
| 254 |
+
// Server doesn't support prompts - that's okay
|
| 255 |
+
console.log(`Server ${serverId} does not support prompts:`, error instanceof Error ? error.message : String(error));
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
// Update connection state
|
| 259 |
connection.isConnected = true;
|
| 260 |
connection.tools = toolsResult.tools;
|
| 261 |
+
connection.resources = resources;
|
| 262 |
+
connection.prompts = prompts;
|
| 263 |
connection.lastError = undefined;
|
| 264 |
connection.lastConnected = new Date();
|
| 265 |
|
|
|
|
| 311 |
return allTools;
|
| 312 |
}
|
| 313 |
|
| 314 |
+
// Get all resources from all connected servers
|
| 315 |
+
getAllResources() {
|
| 316 |
+
const allResources: any[] = [];
|
| 317 |
+
|
| 318 |
+
for (const connection of this.connections.values()) {
|
| 319 |
+
if (connection.isConnected && connection.config.enabled) {
|
| 320 |
+
allResources.push(...connection.resources);
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
return allResources;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
// Get all prompts from all connected servers
|
| 328 |
+
getAllPrompts() {
|
| 329 |
+
const allPrompts: any[] = [];
|
| 330 |
+
|
| 331 |
+
for (const connection of this.connections.values()) {
|
| 332 |
+
if (connection.isConnected && connection.config.enabled) {
|
| 333 |
+
allPrompts.push(...connection.prompts);
|
| 334 |
+
}
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
return allPrompts;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
// Call a tool on an MCP server
|
| 341 |
async callTool(
|
| 342 |
serverId: string,
|
src/types/mcp.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
-
import type {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
export interface MCPServerConfig {
|
| 4 |
id: string;
|
|
@@ -18,6 +22,8 @@ export interface MCPServerConnection {
|
|
| 18 |
config: MCPServerConfig;
|
| 19 |
isConnected: boolean;
|
| 20 |
tools: MCPTool[];
|
|
|
|
|
|
|
| 21 |
lastError?: string;
|
| 22 |
lastConnected?: Date;
|
| 23 |
}
|
|
|
|
| 1 |
+
import type {
|
| 2 |
+
Tool as MCPTool,
|
| 3 |
+
Resource as MCPResource,
|
| 4 |
+
Prompt as MCPPrompt
|
| 5 |
+
} from "@modelcontextprotocol/sdk/types.js";
|
| 6 |
|
| 7 |
export interface MCPServerConfig {
|
| 8 |
id: string;
|
|
|
|
| 22 |
config: MCPServerConfig;
|
| 23 |
isConnected: boolean;
|
| 24 |
tools: MCPTool[];
|
| 25 |
+
resources: MCPResource[];
|
| 26 |
+
prompts: MCPPrompt[];
|
| 27 |
lastError?: string;
|
| 28 |
lastConnected?: Date;
|
| 29 |
}
|