@nifrajs/mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.d.ts +40 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +107 -0
- package/dist/bridge.js.map +1 -0
- package/dist/http.d.ts +27 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +107 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +134 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +287 -0
- package/dist/protocol.js.map +1 -0
- package/dist/react.d.ts +19 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +71 -0
- package/dist/react.js.map +1 -0
- package/dist/server.d.ts +51 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +48 -0
- package/dist/server.js.map +1 -0
- package/dist/tool.d.ts +43 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +53 -0
- package/dist/tool.js.map +1 -0
- package/dist/widget.d.ts +43 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +58 -0
- package/dist/widget.js.map +1 -0
- package/package.json +65 -0
- package/src/bridge.ts +126 -0
- package/src/http.ts +131 -0
- package/src/index.ts +28 -0
- package/src/protocol.ts +397 -0
- package/src/react.ts +87 -0
- package/src/server.ts +85 -0
- package/src/tool.ts +102 -0
- package/src/widget.ts +84 -0
package/src/widget.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineMcpWidget` — author an MCP Apps (`ui://`) interactive widget for a nifra MCP server.
|
|
3
|
+
*
|
|
4
|
+
* A widget is ONE self-contained HTML document, served as an MCP resource with MIME {@link UI_MIME}. The
|
|
5
|
+
* host renders it in a sandboxed iframe and pushes the linked tool's `structuredContent` in over the
|
|
6
|
+
* {@link ./bridge.ts} bridge — so the author writes plain markup + `mcpApp.onData(render)`, no transport
|
|
7
|
+
* glue. Link it to a tool with {@link ./tool.ts | defineMcpTool({ widget })}, which stamps the tool's
|
|
8
|
+
* `_meta.ui.resourceUri` from {@link McpWidget.meta}.
|
|
9
|
+
*
|
|
10
|
+
* SECURITY: `structuredContent` (and any theme) arriving over the bridge is UNTRUSTED — escape it before
|
|
11
|
+
* writing to `innerHTML`, or render with a framework that escapes (React/Preact, or `@nifrajs/mcp/react`).
|
|
12
|
+
* The sandboxed iframe limits blast radius, but a widget that builds markup by string concatenation can
|
|
13
|
+
* still XSS itself; the bridge passes data through, it does not sanitize.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { bridgeScript } from "./bridge.ts"
|
|
17
|
+
import { type McpResource, UI_MIME } from "./protocol.ts"
|
|
18
|
+
|
|
19
|
+
export interface DefineMcpWidgetOptions {
|
|
20
|
+
/** The widget's resource URI — MUST use the `ui://` scheme, e.g. `ui://orders/table`. */
|
|
21
|
+
readonly uri: string
|
|
22
|
+
/** Human-readable name (shown by hosts that list resources). */
|
|
23
|
+
readonly name: string
|
|
24
|
+
readonly description?: string
|
|
25
|
+
/** Document title (defaults to `name`). */
|
|
26
|
+
readonly title?: string
|
|
27
|
+
/** Extra `<head>` content — `<style>`, `<meta>`, fonts. The bridge `<script>` is injected for you. */
|
|
28
|
+
readonly head?: string
|
|
29
|
+
/** The widget body: markup plus a `<script>` that renders from `mcpApp.onData(...)` and may
|
|
30
|
+
* `mcpApp.callTool(...)`. The bridge global is defined before this runs. */
|
|
31
|
+
readonly html: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The MCP Apps `_meta.ui.resourceUri` link, emitted nested (current spec) AND flat (the deprecated
|
|
35
|
+
* `ui/resourceUri`, still read by some hosts) so a tool renders across the transition. */
|
|
36
|
+
export function uiResourceMeta(uri: string): Record<string, unknown> {
|
|
37
|
+
return { ui: { resourceUri: uri }, "ui/resourceUri": uri }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A widget: the resource to register on the server, its `ui://` URI, and the `_meta` link for its tool. */
|
|
41
|
+
export interface McpWidget {
|
|
42
|
+
readonly uri: string
|
|
43
|
+
readonly resource: McpResource
|
|
44
|
+
readonly meta: Record<string, unknown>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function escapeHtml(text: string): string {
|
|
48
|
+
return text.replace(/[&<>]/g, (c) => (c === "&" ? "&" : c === "<" ? "<" : ">"))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Assemble the full self-contained widget document (bridge inlined in `<head>` so body scripts can use
|
|
52
|
+
* `mcpApp` immediately). */
|
|
53
|
+
export function widgetDocument(opts: DefineMcpWidgetOptions): string {
|
|
54
|
+
const title = escapeHtml(opts.title ?? opts.name)
|
|
55
|
+
return `<!doctype html>
|
|
56
|
+
<html lang="en">
|
|
57
|
+
<head>
|
|
58
|
+
<meta charset="utf-8" />
|
|
59
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
60
|
+
<title>${title}</title>
|
|
61
|
+
<script>${bridgeScript()}</script>
|
|
62
|
+
${opts.head ?? ""}
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
${opts.html}
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
68
|
+
`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function defineMcpWidget(opts: DefineMcpWidgetOptions): McpWidget {
|
|
72
|
+
if (!opts.uri.startsWith("ui://")) {
|
|
73
|
+
throw new Error(`defineMcpWidget: uri must use the ui:// scheme (got "${opts.uri}")`)
|
|
74
|
+
}
|
|
75
|
+
const html = widgetDocument(opts)
|
|
76
|
+
const resource: McpResource = {
|
|
77
|
+
uri: opts.uri,
|
|
78
|
+
name: opts.name,
|
|
79
|
+
...(opts.description !== undefined ? { description: opts.description } : {}),
|
|
80
|
+
mimeType: UI_MIME,
|
|
81
|
+
read: () => Promise.resolve({ text: html, mimeType: UI_MIME }),
|
|
82
|
+
}
|
|
83
|
+
return { uri: opts.uri, resource, meta: uiResourceMeta(opts.uri) }
|
|
84
|
+
}
|