Compare commits

...

11 Commits

Author SHA1 Message Date
paoloricciuti
96d7e7f26b fix: re-add mcp-ui resource 2026-01-13 17:31:12 +01:00
paoloricciuti
2d2e72d467 fix: add baseUriDomains 2026-01-13 16:48:56 +01:00
Paolo Ricciuti
a7039b59b8 fix: send fixed height 2026-01-08 18:01:56 +01:00
paoloricciuti
8b46b893f0 fix: duh 2026-01-08 16:19:50 +01:00
paoloricciuti
b14d66163b fix: handle size changed 2026-01-08 15:38:30 +01:00
paoloricciuti
7c7e2c99c6 fix: add csp info on the resource 2026-01-08 15:10:33 +01:00
paoloricciuti
24837079d7 chore: remove csp meta 2026-01-08 01:08:50 +01:00
paoloricciuti
8580923e6f fix: remove logs 2026-01-08 00:24:02 +01:00
paoloricciuti
81e24d823f fix: test 2026-01-07 20:44:06 +01:00
paoloricciuti
347a41a044 fix: update _meta field with latest version of the spec 2026-01-07 20:17:47 +01:00
paoloricciuti
8c55e16491 feat: expose playground link as MCP App 2026-01-07 14:46:09 +01:00
5 changed files with 312 additions and 41 deletions

View File

@@ -0,0 +1,5 @@
---
'@sveltejs/mcp': patch
---
feat: expose playground link as MCP App

View File

@@ -67,6 +67,24 @@ describe('playground-link tool', () => {
);
});
it('should have tool _meta with resource URI for MCP Apps hosts', async () => {
const tools = await session.listTools();
const playground_tool = tools.tools.find((t) => t.name === 'playground-link');
expect(playground_tool).toBeDefined();
expect(playground_tool?._meta).toStrictEqual({
ui: { resourceUri: 'ui://svelte/playground-link' },
});
});
it('should expose a resource for MCP Apps hosts', async () => {
const resources = await session.listResources();
const playground_resource = resources.resources.find(
(r) => r.uri === 'ui://svelte/playground-link',
);
expect(playground_resource).toBeDefined();
expect(playground_resource?.name).toBe('playground-link-ui');
});
it('should not create a playground link if App.svelte is missing', async () => {
const result = await session.callTool<{ url: string }>('playground-link', {
name: 'My Playground',

View File

@@ -1,8 +1,8 @@
import type { SvelteMcp } from '../../index.js';
import * as v from 'valibot';
import { icons } from '../../icons/index.js';
import { createUIResource } from '@mcp-ui/server';
import { tool } from 'tmcp/utils';
import * as v from 'valibot';
import { icons } from '../../icons/index.js';
import type { SvelteMcp } from '../../index.js';
async function compress_and_encode_text(input: string) {
const reader = new Blob([input]).stream().pipeThrough(new CompressionStream('gzip')).getReader();
@@ -97,7 +97,106 @@ export async function playground_link_handler({
};
}
// Create the UI resource for MCP Apps hosts (with adapter)
// This will be registered as a resource that MCP Apps hosts can fetch
const playground_ui_resource = createUIResource({
uri: 'ui://svelte/playground-link',
encoding: 'text',
resourceProps: {
_meta: {
ui: {
csp: {
connectDomains: ['https://svelte.dev'],
resourceDomains: ['https://svelte.dev'],
frameDomains: ['https://svelte.dev'],
baseUriDomains: ['https://svelte.dev'],
},
},
},
},
content: {
type: 'rawHtml',
// This is a placeholder HTML - the actual iframe URL will be set per-request
// MCP Apps hosts receive the tool input/output via postMessage
htmlString: `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; }
iframe { width: 100%; height: 100%; border: none; display: none; }
.loading { display: flex; align-items: center; justify-content: center; height: 100%; font-family: system-ui, sans-serif; color: #666; }
</style>
</head>
<body>
<div class="loading" id="loading">Loading playground...</div>
<iframe id="playground" allow="clipboard-write"></iframe>
<script>
function size_changed() {
const width = document.body.scrollWidth;
window.parent.postMessage({
jsonrpc: '2.0',
method: 'ui/notifications/size-changed',
params: {
width,
height: 800
}
}, '*');
}
// Signal that the widget is ready
window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');
// Listen for render data from the adapter (for MCP Apps hosts)
window.addEventListener('message', (event) => {
if (event.data.type === 'ui-lifecycle-iframe-render-data') {
const renderData = event.data.payload.renderData || {};
const toolOutput = renderData.toolOutput;
// The tool output contains the iframe URL
if (toolOutput && toolOutput.structuredContent && toolOutput.structuredContent.url) {
const iframe = document.getElementById('playground');
const loading = document.getElementById('loading');
// Convert the URL to embed URL
const embedUrl = toolOutput.structuredContent.url.replace('/playground#', '/playground/embed#');
iframe.src = embedUrl;
iframe.style.display = 'block';
iframe.addEventListener("load", () => {
size_changed();
});
loading.style.display = 'none';
}
}
});
</script>
</body>
</html>`,
},
uiMetadata: {
'preferred-frame-size': ['100%', '1200px'],
},
adapters: {
mcpApps: { enabled: true },
},
});
export function playground_link(server: SvelteMcp) {
// Register the UI resource so MCP Apps hosts can fetch it
server.resource(
{
name: 'playground-link-ui',
description: 'UI resource for the Svelte Playground widget',
uri: playground_ui_resource.resource.uri,
icons,
},
() => {
return {
contents: [playground_ui_resource.resource],
};
},
);
server.tool(
{
name: 'playground-link',
@@ -112,6 +211,12 @@ export function playground_link(server: SvelteMcp) {
openWorldHint: false,
},
icons,
// For MCP Apps hosts - points to the registered resource
_meta: {
ui: {
resourceUri: playground_ui_resource.resource.uri,
},
},
},
async ({ files, name, tailwind }) => {
if (server.ctx.sessionId && server.ctx.custom?.track) {
@@ -125,6 +230,7 @@ export function playground_link(server: SvelteMcp) {
type: 'text',
text: JSON.stringify({ url: result.url }),
},
// Embedded resource for MCP-UI hosts (no adapter, uses externalUrl)
createUIResource({
uri: 'ui://svelte/playground-link',
content: {

216
pnpm-lock.yaml generated
View File

@@ -10,8 +10,8 @@ catalogs:
specifier: ^0.71.0
version: 0.71.0
'@mcp-ui/server':
specifier: ^5.12.0
version: 5.12.0
specifier: ^5.16.3
version: 5.16.3
'@modelcontextprotocol/inspector':
specifier: ^0.18.0
version: 0.18.0
@@ -316,7 +316,7 @@ importers:
dependencies:
'@mcp-ui/server':
specifier: catalog:ai
version: 5.12.0
version: 5.16.3(hono@4.11.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76)
'@sveltejs/mcp-schema':
specifier: workspace:^
version: link:../mcp-schema
@@ -1018,8 +1018,21 @@ packages:
engines: {node: '>=18'}
hasBin: true
'@mcp-ui/server@5.12.0':
resolution: {integrity: sha512-ZAAHsvzfrBgA0gkyIOjoKNTBTsD0VSJT4KXKHe+Fx/kBASctG6mrzK5gvxD/LLLliantN2UWLTKtEeI4DH4FRQ==}
'@mcp-ui/server@5.16.3':
resolution: {integrity: sha512-MZttML9tQC1B8f4SPgoT1BORfCDXqs4Ywb/mQ0wSwIYS9AuIWFLj1KXmvqC/zrG0SlNMlyvMSxEX/eVYsIoZaA==}
'@modelcontextprotocol/ext-apps@0.2.2':
resolution: {integrity: sha512-h8sN3QIBLqMsRXjKL76M5VmBQf3N0I1G1DiDiSYAgtdynYQctHqCs79WEo1d5wClyZVYBWXdRcxgiR/WBfSOqw==}
peerDependencies:
'@modelcontextprotocol/sdk': ^1.24.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
zod: ^3.25.0 || ^4.0.0
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
'@modelcontextprotocol/inspector-cli@0.18.0':
resolution: {integrity: sha512-QMPjKx8zKmX17S1LF2gWuwbYglKexkdgB0HhKZFXzGrQ0MYoKUsIgokMyV48xr4LipaLS3b2v3ut3nV/jhWeSg==}
@@ -1038,10 +1051,6 @@ packages:
engines: {node: '>=22.7.5'}
hasBin: true
'@modelcontextprotocol/sdk@1.18.2':
resolution: {integrity: sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg==}
engines: {node: '>=18'}
'@modelcontextprotocol/sdk@1.25.1':
resolution: {integrity: sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==}
engines: {node: '>=18'}
@@ -1070,6 +1079,61 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@oven/bun-darwin-aarch64@1.3.5':
resolution: {integrity: sha512-8GvNtMo0NINM7Emk9cNAviCG3teEgr3BUX9be0+GD029zIagx2Sf54jMui1Eu1IpFm7nWHODuLEefGOQNaJ0gQ==}
cpu: [arm64]
os: [darwin]
'@oven/bun-darwin-x64-baseline@1.3.5':
resolution: {integrity: sha512-p5q3rJk48qhLuLBOFehVc+kqCE03YrswTc6NCxbwsxiwfySXwcAvpF2KWKF/ZZObvvR8hCCvqe1F81b2p5r2dg==}
cpu: [x64]
os: [darwin]
'@oven/bun-darwin-x64@1.3.5':
resolution: {integrity: sha512-r33eHQOHAwkuiBJIwmkXIyqONQOQMnd1GMTpDzaxx9vf9+svby80LZO9Hcm1ns6KT/TBRFyODC/0loA7FAaffg==}
cpu: [x64]
os: [darwin]
'@oven/bun-linux-aarch64-musl@1.3.5':
resolution: {integrity: sha512-HKBeUlJdNduRkzJKZ5DXM+pPqntfC50/Hu2X65jVX0Y7hu/6IC8RaUTqpr8FtCZqqmc9wDK0OTL+Mbi9UQIKYQ==}
cpu: [arm64]
os: [linux]
'@oven/bun-linux-aarch64@1.3.5':
resolution: {integrity: sha512-zkcHPI23QxJ1TdqafhgkXt1NOEN8o5C460sVeNnrhfJ43LwZgtfcvcQE39x/pBedu67fatY8CU0iY00nOh46ZQ==}
cpu: [arm64]
os: [linux]
'@oven/bun-linux-x64-baseline@1.3.5':
resolution: {integrity: sha512-FeCQyBU62DMuB0nn01vPnf3McXrKOsrK9p7sHaBFYycw0mmoU8kCq/WkBkGMnLuvQljJSyen8QBTx+fXdNupWg==}
cpu: [x64]
os: [linux]
'@oven/bun-linux-x64-musl-baseline@1.3.5':
resolution: {integrity: sha512-TJiYC7KCr0XxFTsxgwQOeE7dncrEL/RSyL0EzSL3xRkrxJMWBCvCSjQn7LV1i6T7hFst0+3KoN3VWvD5BinqHA==}
cpu: [x64]
os: [linux]
'@oven/bun-linux-x64-musl@1.3.5':
resolution: {integrity: sha512-XkCCHkByYn8BIDvoxnny898znju4xnW2kvFE8FT5+0Y62cWdcBGMZ9RdsEUTeRz16k8hHtJpaSfLcEmNTFIwRQ==}
cpu: [x64]
os: [linux]
'@oven/bun-linux-x64@1.3.5':
resolution: {integrity: sha512-n7zhKTSDZS0yOYg5Rq8easZu5Y/o47sv0c7yGr2ciFdcie9uYV55fZ7QMqhWMGK33ezCSikh5EDkUMCIvfWpjA==}
cpu: [x64]
os: [linux]
'@oven/bun-windows-x64-baseline@1.3.5':
resolution: {integrity: sha512-rtVQB9/1XK8FWJgFtsOthbPifRMYypgJwxu+pK3NHx8WvFKmq7HcPDqNr8xLzGULjQEO7eAo2aOZfONOwYz+5g==}
cpu: [x64]
os: [win32]
'@oven/bun-windows-x64@1.3.5':
resolution: {integrity: sha512-T3xkODItb/0ftQPFsZDc7EAX2D6A4TEazQ2YZyofZToO8Q7y8YT8ooWdhd0BQiTCd66uEvgE1DCZetynwg2IoA==}
cpu: [x64]
os: [win32]
'@oxc-project/types@0.101.0':
resolution: {integrity: sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==}
@@ -1581,11 +1645,21 @@ packages:
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-arm64@4.55.1':
resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.52.2':
resolution: {integrity: sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.55.1':
resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.52.2':
resolution: {integrity: sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==}
cpu: [arm64]
@@ -1611,6 +1685,11 @@ packages:
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.55.1':
resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.52.2':
resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==}
cpu: [arm64]
@@ -1646,6 +1725,11 @@ packages:
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.55.1':
resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.52.2':
resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==}
cpu: [x64]
@@ -1676,6 +1760,11 @@ packages:
cpu: [x64]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.55.1':
resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==}
cpu: [x64]
os: [win32]
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
@@ -4346,11 +4435,6 @@ packages:
zimmerframe@1.1.4:
resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==}
zod-to-json-schema@3.24.6:
resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==}
peerDependencies:
zod: ^3.24.1
zod-to-json-schema@3.25.0:
resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==}
peerDependencies:
@@ -4938,11 +5022,42 @@ snapshots:
- encoding
- supports-color
'@mcp-ui/server@5.12.0':
'@mcp-ui/server@5.16.3(hono@4.11.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76)':
dependencies:
'@modelcontextprotocol/sdk': 1.18.2
'@modelcontextprotocol/ext-apps': 0.2.2(@modelcontextprotocol/sdk@1.25.1(hono@4.11.1)(zod@3.25.76))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76)
'@modelcontextprotocol/sdk': 1.25.1(hono@4.11.1)(zod@3.25.76)
transitivePeerDependencies:
- '@cfworker/json-schema'
- hono
- react
- react-dom
- supports-color
- zod
'@modelcontextprotocol/ext-apps@0.2.2(@modelcontextprotocol/sdk@1.25.1(hono@4.11.1)(zod@3.25.76))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.25.76)':
dependencies:
'@modelcontextprotocol/sdk': 1.25.1(hono@4.11.1)(zod@3.25.76)
prettier: 3.6.2
zod: 3.25.76
optionalDependencies:
'@oven/bun-darwin-aarch64': 1.3.5
'@oven/bun-darwin-x64': 1.3.5
'@oven/bun-darwin-x64-baseline': 1.3.5
'@oven/bun-linux-aarch64': 1.3.5
'@oven/bun-linux-aarch64-musl': 1.3.5
'@oven/bun-linux-x64': 1.3.5
'@oven/bun-linux-x64-baseline': 1.3.5
'@oven/bun-linux-x64-musl': 1.3.5
'@oven/bun-linux-x64-musl-baseline': 1.3.5
'@oven/bun-windows-x64': 1.3.5
'@oven/bun-windows-x64-baseline': 1.3.5
'@rollup/rollup-darwin-arm64': 4.55.1
'@rollup/rollup-darwin-x64': 4.55.1
'@rollup/rollup-linux-arm64-gnu': 4.55.1
'@rollup/rollup-linux-x64-gnu': 4.55.1
'@rollup/rollup-win32-x64-msvc': 4.55.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@modelcontextprotocol/inspector-cli@0.18.0(hono@4.11.1)(zod@3.25.76)':
dependencies:
@@ -5031,23 +5146,6 @@ snapshots:
- typescript
- utf-8-validate
'@modelcontextprotocol/sdk@1.18.2':
dependencies:
ajv: 6.12.6
content-type: 1.0.5
cors: 2.8.5
cross-spawn: 7.0.6
eventsource: 3.0.7
eventsource-parser: 3.0.6
express: 5.1.0
express-rate-limit: 7.5.1(express@5.1.0)
pkce-challenge: 5.0.0
raw-body: 3.0.1
zod: 3.25.76
zod-to-json-schema: 3.24.6(zod@3.25.76)
transitivePeerDependencies:
- supports-color
'@modelcontextprotocol/sdk@1.25.1(hono@4.11.1)(zod@3.25.76)':
dependencies:
'@hono/node-server': 1.19.7(hono@4.11.1)
@@ -5091,6 +5189,39 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@oven/bun-darwin-aarch64@1.3.5':
optional: true
'@oven/bun-darwin-x64-baseline@1.3.5':
optional: true
'@oven/bun-darwin-x64@1.3.5':
optional: true
'@oven/bun-linux-aarch64-musl@1.3.5':
optional: true
'@oven/bun-linux-aarch64@1.3.5':
optional: true
'@oven/bun-linux-x64-baseline@1.3.5':
optional: true
'@oven/bun-linux-x64-musl-baseline@1.3.5':
optional: true
'@oven/bun-linux-x64-musl@1.3.5':
optional: true
'@oven/bun-linux-x64@1.3.5':
optional: true
'@oven/bun-windows-x64-baseline@1.3.5':
optional: true
'@oven/bun-windows-x64@1.3.5':
optional: true
'@oxc-project/types@0.101.0': {}
'@petamoriken/float16@3.9.2':
@@ -5472,9 +5603,15 @@ snapshots:
'@rollup/rollup-darwin-arm64@4.52.2':
optional: true
'@rollup/rollup-darwin-arm64@4.55.1':
optional: true
'@rollup/rollup-darwin-x64@4.52.2':
optional: true
'@rollup/rollup-darwin-x64@4.55.1':
optional: true
'@rollup/rollup-freebsd-arm64@4.52.2':
optional: true
@@ -5490,6 +5627,9 @@ snapshots:
'@rollup/rollup-linux-arm64-gnu@4.52.2':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.55.1':
optional: true
'@rollup/rollup-linux-arm64-musl@4.52.2':
optional: true
@@ -5511,6 +5651,9 @@ snapshots:
'@rollup/rollup-linux-x64-gnu@4.52.2':
optional: true
'@rollup/rollup-linux-x64-gnu@4.55.1':
optional: true
'@rollup/rollup-linux-x64-musl@4.52.2':
optional: true
@@ -5529,6 +5672,9 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.52.2':
optional: true
'@rollup/rollup-win32-x64-msvc@4.55.1':
optional: true
'@rtsao/scc@1.1.0': {}
'@standard-schema/spec@1.0.0': {}
@@ -8304,10 +8450,6 @@ snapshots:
zimmerframe@1.1.4: {}
zod-to-json-schema@3.24.6(zod@3.25.76):
dependencies:
zod: 3.25.76
zod-to-json-schema@3.25.0(zod@3.25.76):
dependencies:
zod: 3.25.76

View File

@@ -5,7 +5,7 @@ packages:
catalogs:
ai:
'@anthropic-ai/sdk': ^0.71.0
'@mcp-ui/server': ^5.12.0
'@mcp-ui/server': ^5.16.3
'@modelcontextprotocol/inspector': ^0.18.0
lint:
'@eslint/compat': ^2.0.0