-
Notifications
You must be signed in to change notification settings - Fork 267
Expand file tree
/
Copy pathmcp-app.ts
More file actions
112 lines (93 loc) · 3.46 KB
/
mcp-app.ts
File metadata and controls
112 lines (93 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
* @file App that demonstrates a few features using MCP Apps SDK with vanilla JS.
*/
import { App } from "@modelcontextprotocol/ext-apps";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import "./global.css";
import "./mcp-app.css";
const log = {
info: console.log.bind(console, "[APP]"),
warn: console.warn.bind(console, "[APP]"),
error: console.error.bind(console, "[APP]"),
};
function extractTime(result: CallToolResult): string {
// Prefer structuredContent (STRUCTURED_CONTENT_ONLY mode), fall back to parsing text
let data: { time: string };
if (result.structuredContent) {
data = result.structuredContent as { time: string };
} else {
const text = result.content!
.filter((c): c is { type: "text"; text: string } => c.type === "text")
.map((c) => c.text)
.join("");
try {
data = JSON.parse(text) as { time: string };
} catch (e) {
log.error("Failed to parse tool result:", text, e);
return "[PARSE ERROR]";
}
}
return data.time;
}
// Get element references
const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;
const messageText = document.getElementById("message-text") as HTMLTextAreaElement;
const sendMessageBtn = document.getElementById("send-message-btn")!;
const logText = document.getElementById("log-text") as HTMLInputElement;
const sendLogBtn = document.getElementById("send-log-btn")!;
const linkUrl = document.getElementById("link-url") as HTMLInputElement;
const openLinkBtn = document.getElementById("open-link-btn")!;
// Create app instance
const app = new App({ name: "Get Time App", version: "1.0.0" });
app.onteardown = async () => {
log.info("App is being torn down");
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate cleanup work
log.info("App teardown complete");
return {};
};
// Register handlers BEFORE connecting
app.ontoolinput = (params) => {
log.info("Received tool call input:", params);
};
app.ontoolresult = (result) => {
log.info("Received tool call result:", result);
serverTimeEl.textContent = extractTime(result);
};
app.onerror = log.error;
// Add event listeners
getTimeBtn.addEventListener("click", async () => {
try {
log.info("Calling get-time tool...");
const result = await app.callServerTool({ name: "get-time", arguments: {} });
log.info("get-time result:", result);
serverTimeEl.textContent = extractTime(result);
} catch (e) {
log.error(e);
serverTimeEl.textContent = "[ERROR]";
}
});
sendMessageBtn.addEventListener("click", async () => {
const signal = AbortSignal.timeout(5000);
try {
log.info("Sending message text to Host:", messageText.value);
const { isError } = await app.sendMessage(
{ role: "user", content: [{ type: "text", text: messageText.value }] },
{ signal },
);
log.info("Message", isError ? "rejected" : "accepted");
} catch (e) {
log.error("Message send error:", signal.aborted ? "timed out" : e);
}
});
sendLogBtn.addEventListener("click", async () => {
log.info("Sending log text to Host:", logText.value);
await app.sendLog({ level: "info", data: logText.value });
});
openLinkBtn.addEventListener("click", async () => {
log.info("Sending open link request to Host:", linkUrl.value);
const { isError } = await app.openLink({ url: linkUrl.value });
log.info("Open link request", isError ? "rejected" : "accepted");
});
// Connect to host
app.connect();