Skip to content

Commit fe67f79

Browse files
committed
feat: MCP message type
1 parent e1ba8f1 commit fe67f79

File tree

4 files changed

+103
-21
lines changed

4 files changed

+103
-21
lines changed

app/mcp/actions.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import { createClient, executeRequest } from "./client";
44
import { MCPClientLogger } from "./logger";
55
import conf from "./mcp_config.json";
6+
import { McpRequestMessage } from "./types";
67

7-
const logger = new MCPClientLogger("MCP Server");
8+
const logger = new MCPClientLogger("MCP Actions");
89

910
// Use Map to store all clients
1011
const clientsMap = new Map<string, any>();
@@ -51,7 +52,10 @@ export async function initializeMcpClients() {
5152
}
5253

5354
// Execute MCP request
54-
export async function executeMcpAction(clientId: string, request: any) {
55+
export async function executeMcpAction(
56+
clientId: string,
57+
request: McpRequestMessage,
58+
) {
5559
try {
5660
// Find the corresponding client
5761
const client = clientsMap.get(clientId);
@@ -61,6 +65,7 @@ export async function executeMcpAction(clientId: string, request: any) {
6165
}
6266

6367
logger.info(`Executing MCP request for ${clientId}`);
68+
6469
// Execute request and return result
6570
return await executeRequest(client, request);
6671
} catch (error) {

app/mcp/client.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
22
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
33
import { MCPClientLogger } from "./logger";
4+
import { McpRequestMessage } from "./types";
45
import { z } from "zod";
56

67
export interface ServerConfig {
@@ -79,6 +80,9 @@ export async function listPrimitives(client: Client) {
7980
}
8081

8182
/** Execute a request */
82-
export async function executeRequest(client: Client, request: any) {
83+
export async function executeRequest(
84+
client: Client,
85+
request: McpRequestMessage,
86+
) {
8387
return client.request(request, z.any());
8488
}

app/mcp/types.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// ref: https://spec.modelcontextprotocol.io/specification/basic/messages/
2+
3+
import { z } from "zod";
4+
5+
export interface McpRequestMessage {
6+
jsonrpc?: "2.0";
7+
id?: string | number;
8+
method: "tools/call" | string;
9+
params?: {
10+
[key: string]: unknown;
11+
};
12+
}
13+
14+
export const McpRequestMessageSchema: z.ZodType<McpRequestMessage> = z.object({
15+
jsonrpc: z.literal("2.0").optional(),
16+
id: z.union([z.string(), z.number()]).optional(),
17+
method: z.string(),
18+
params: z.record(z.unknown()).optional(),
19+
});
20+
21+
export interface McpResponseMessage {
22+
jsonrpc?: "2.0";
23+
id?: string | number;
24+
result?: {
25+
[key: string]: unknown;
26+
};
27+
error?: {
28+
code: number;
29+
message: string;
30+
data?: unknown;
31+
};
32+
}
33+
34+
export const McpResponseMessageSchema: z.ZodType<McpResponseMessage> = z.object(
35+
{
36+
jsonrpc: z.literal("2.0").optional(),
37+
id: z.union([z.string(), z.number()]).optional(),
38+
result: z.record(z.unknown()).optional(),
39+
error: z
40+
.object({
41+
code: z.number(),
42+
message: z.string(),
43+
data: z.unknown().optional(),
44+
})
45+
.optional(),
46+
},
47+
);
48+
49+
export interface McpNotifications {
50+
jsonrpc?: "2.0";
51+
method: string;
52+
params?: {
53+
[key: string]: unknown;
54+
};
55+
}
56+
57+
export const McpNotificationsSchema: z.ZodType<McpNotifications> = z.object({
58+
jsonrpc: z.literal("2.0").optional(),
59+
method: z.string(),
60+
params: z.record(z.unknown()).optional(),
61+
});

app/store/chat.ts

+30-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { getMessageTextContent, trimTopic } from "../utils";
1+
import {
2+
getMessageTextContent,
3+
isDalle3,
4+
safeLocalStorage,
5+
trimTopic,
6+
} from "../utils";
27

38
import { indexedDBStorage } from "@/app/utils/indexedDB-storage";
49
import { nanoid } from "nanoid";
@@ -14,14 +19,13 @@ import {
1419
DEFAULT_INPUT_TEMPLATE,
1520
DEFAULT_MODELS,
1621
DEFAULT_SYSTEM_TEMPLATE,
22+
GEMINI_SUMMARIZE_MODEL,
1723
KnowledgeCutOffDate,
24+
ServiceProvider,
1825
StoreKey,
1926
SUMMARIZE_MODEL,
20-
GEMINI_SUMMARIZE_MODEL,
21-
ServiceProvider,
2227
} from "../constant";
2328
import Locale, { getLang } from "../locales";
24-
import { isDalle3, safeLocalStorage } from "../utils";
2529
import { prettyObject } from "../utils/format";
2630
import { createPersistStore } from "../utils/store";
2731
import { estimateTokenLength } from "../utils/token";
@@ -55,6 +59,7 @@ export type ChatMessage = RequestMessage & {
5559
model?: ModelType;
5660
tools?: ChatMessageTool[];
5761
audio_url?: string;
62+
isMcpResponse?: boolean;
5863
};
5964

6065
export function createMessage(override: Partial<ChatMessage>): ChatMessage {
@@ -368,20 +373,22 @@ export const useChatStore = createPersistStore(
368373
get().summarizeSession(false, targetSession);
369374
},
370375

371-
async onUserInput(content: string, attachImages?: string[]) {
376+
async onUserInput(
377+
content: string,
378+
attachImages?: string[],
379+
isMcpResponse?: boolean,
380+
) {
372381
const session = get().currentSession();
373382
const modelConfig = session.mask.modelConfig;
374383

375-
const userContent = fillTemplateWith(content, modelConfig);
376-
console.log("[User Input] after template: ", userContent);
377-
378-
let mContent: string | MultimodalContent[] = userContent;
384+
// MCP Response no need to fill template
385+
let mContent: string | MultimodalContent[] = isMcpResponse
386+
? content
387+
: fillTemplateWith(content, modelConfig);
379388

380-
if (attachImages && attachImages.length > 0) {
389+
if (!isMcpResponse && attachImages && attachImages.length > 0) {
381390
mContent = [
382-
...(userContent
383-
? [{ type: "text" as const, text: userContent }]
384-
: []),
391+
...(content ? [{ type: "text" as const, text: content }] : []),
385392
...attachImages.map((url) => ({
386393
type: "image_url" as const,
387394
image_url: { url },
@@ -392,6 +399,7 @@ export const useChatStore = createPersistStore(
392399
let userMessage: ChatMessage = createMessage({
393400
role: "user",
394401
content: mContent,
402+
isMcpResponse,
395403
});
396404

397405
const botMessage: ChatMessage = createMessage({
@@ -770,9 +778,10 @@ export const useChatStore = createPersistStore(
770778
lastInput,
771779
});
772780
},
781+
782+
/** check if the message contains MCP JSON and execute the MCP action */
773783
checkMcpJson(message: ChatMessage) {
774-
const content =
775-
typeof message.content === "string" ? message.content : "";
784+
const content = getMessageTextContent(message);
776785
if (isMcpJson(content)) {
777786
try {
778787
const mcpRequest = extractMcpJson(content);
@@ -782,11 +791,14 @@ export const useChatStore = createPersistStore(
782791
executeMcpAction(mcpRequest.clientId, mcpRequest.mcp)
783792
.then((result) => {
784793
console.log("[MCP Response]", result);
785-
// 直接使用onUserInput发送结果
786-
get().onUserInput(
794+
const mcpResponse =
787795
typeof result === "object"
788796
? JSON.stringify(result)
789-
: String(result),
797+
: String(result);
798+
get().onUserInput(
799+
`\`\`\`json:mcp:${mcpRequest.clientId}\n${mcpResponse}\n\`\`\``,
800+
[],
801+
true,
790802
);
791803
})
792804
.catch((error) => showToast(String(error)));

0 commit comments

Comments
 (0)