| | <script lang="ts"> |
| | import { onMount } from "svelte"; |
| | import { |
| | App, |
| | applyDocumentTheme, |
| | applyHostFonts, |
| | applyHostStyleVariables, |
| | type McpUiHostContext, |
| | } from "@modelcontextprotocol/ext-apps"; |
| | import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; |
| | |
| | function extractTime(result: CallToolResult): string { |
| | const { text } = result.content?.find((c) => c.type === "text")!; |
| | return text; |
| | } |
| | |
| | |
| | let app = $state<App | null>(null); |
| | let hostContext = $state<McpUiHostContext | undefined>(); |
| | let serverTime = $state("Loading..."); |
| | let messageText = $state("This is message text."); |
| | let logText = $state("This is log text."); |
| | let linkUrl = $state("https://modelcontextprotocol.io/"); |
| | |
| | |
| | $effect(() => { |
| | if (hostContext?.theme) { |
| | applyDocumentTheme(hostContext.theme); |
| | } |
| | if (hostContext?.styles?.variables) { |
| | applyHostStyleVariables(hostContext.styles.variables); |
| | } |
| | if (hostContext?.styles?.css?.fonts) { |
| | applyHostFonts(hostContext.styles.css.fonts); |
| | } |
| | }); |
| | |
| | onMount(async () => { |
| | const instance = new App({ name: "Get Time App", version: "1.0.0" }); |
| | |
| | instance.ontoolinput = (params) => { |
| | console.info("Received tool call input:", params); |
| | }; |
| | |
| | instance.ontoolresult = (result) => { |
| | console.info("Received tool call result:", result); |
| | serverTime = extractTime(result); |
| | }; |
| | |
| | instance.ontoolcancelled = (params) => { |
| | console.info("Tool call cancelled:", params.reason); |
| | }; |
| | |
| | instance.onerror = console.error; |
| | |
| | instance.onhostcontextchanged = (params) => { |
| | hostContext = { ...hostContext, ...params }; |
| | }; |
| | |
| | await instance.connect(); |
| | app = instance; |
| | hostContext = instance.getHostContext(); |
| | }); |
| | |
| | async function handleGetTime() { |
| | if (!app) return; |
| | try { |
| | console.info("Calling get-time tool..."); |
| | const result = await app.callServerTool({ name: "get-time", arguments: {} }); |
| | console.info("get-time result:", result); |
| | serverTime = extractTime(result); |
| | } catch (e) { |
| | console.error(e); |
| | serverTime = "[ERROR]"; |
| | } |
| | } |
| | |
| | async function handleSendMessage() { |
| | if (!app) return; |
| | const signal = AbortSignal.timeout(5000); |
| | try { |
| | console.info("Sending message text to Host:", messageText); |
| | const { isError } = await app.sendMessage( |
| | { role: "user", content: [{ type: "text", text: messageText }] }, |
| | { signal }, |
| | ); |
| | console.info("Message", isError ? "rejected" : "accepted"); |
| | } catch (e) { |
| | console.error("Message send error:", signal.aborted ? "timed out" : e); |
| | } |
| | } |
| | |
| | async function handleSendLog() { |
| | if (!app) return; |
| | console.info("Sending log text to Host:", logText); |
| | await app.sendLog({ level: "info", data: logText }); |
| | } |
| | |
| | async function handleOpenLink() { |
| | if (!app) return; |
| | console.info("Sending open link request to Host:", linkUrl); |
| | const { isError } = await app.openLink({ url: linkUrl }); |
| | console.info("Open link request", isError ? "rejected" : "accepted"); |
| | } |
| | </script> |
| |
|
| | <main |
| | class="main" |
| | style:padding={hostContext?.safeAreaInsets && `${hostContext.safeAreaInsets.top}px ${hostContext.safeAreaInsets.right}px ${hostContext.safeAreaInsets.bottom}px ${hostContext.safeAreaInsets.left}px`} |
| | > |
| | <p class="notice">Watch activity in the DevTools console!</p> |
| |
|
| | <div class="action"> |
| | <p><strong>Server Time:</strong> <code id="server-time">{serverTime}</code></p> |
| | <button onclick={handleGetTime}>Get Server Time</button> |
| | </div> |
| |
|
| | <div class="action"> |
| | <textarea bind:value={messageText}></textarea> |
| | <button onclick={handleSendMessage}>Send Message</button> |
| | </div> |
| |
|
| | <div class="action"> |
| | <input type="text" bind:value={logText}> |
| | <button onclick={handleSendLog}>Send Log</button> |
| | </div> |
| |
|
| | <div class="action"> |
| | <input type="url" bind:value={linkUrl}> |
| | <button onclick={handleOpenLink}>Open Link</button> |
| | </div> |
| | </main> |
| |
|
| | <style> |
| | .main { |
| | --color-primary: #2563eb; |
| | --color-primary-hover: #1d4ed8; |
| | --color-notice-bg: #eff6ff; |
| | |
| | width: 100%; |
| | max-width: 425px; |
| | box-sizing: border-box; |
| | |
| | > * { |
| | margin-top: 0; |
| | margin-bottom: 0; |
| | } |
| | |
| | > * + * { |
| | margin-top: 1.5rem; |
| | } |
| | } |
| | |
| | .action { |
| | > * { |
| | margin-top: 0; |
| | margin-bottom: 0; |
| | width: 100%; |
| | } |
| | |
| | > * + * { |
| | margin-top: 0.5rem; |
| | } |
| | |
| | textarea, |
| | input { |
| | font-family: inherit; |
| | font-size: inherit; |
| | } |
| | |
| | button { |
| | padding: 0.5rem 1rem; |
| | border: none; |
| | border-radius: 6px; |
| | color: white; |
| | font-weight: bold; |
| | background-color: var(--color-primary); |
| | cursor: pointer; |
| | |
| | &:hover, |
| | &:focus-visible { |
| | background-color: var(--color-primary-hover); |
| | } |
| | } |
| | } |
| | |
| | .notice { |
| | padding: 0.5rem 0.75rem; |
| | color: var(--color-primary); |
| | text-align: center; |
| | font-style: italic; |
| | background-color: var(--color-notice-bg); |
| | |
| | &::before { |
| | content: "ℹ️ "; |
| | font-style: normal; |
| | } |
| | } |
| | </style> |
| |
|