@nimblebrain/synapse 0.18.0 → 0.20.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/README.md +32 -37
- package/dist/check-JEM5PN64.cjs +435 -0
- package/dist/check-JEM5PN64.cjs.map +1 -0
- package/dist/check-YDZUQT6A.js +429 -0
- package/dist/check-YDZUQT6A.js.map +1 -0
- package/dist/{chunk-VDQZQDU3.cjs → chunk-BNF352SA.cjs} +56 -145
- package/dist/chunk-BNF352SA.cjs.map +1 -0
- package/dist/{chunk-HKRTDGXN.js → chunk-DJS2FKBN.js} +21 -89
- package/dist/chunk-DJS2FKBN.js.map +1 -0
- package/dist/chunk-H3INUKA3.js +32 -0
- package/dist/chunk-H3INUKA3.js.map +1 -0
- package/dist/{chunk-FB7GPBEM.cjs → chunk-HQLSVUOR.cjs} +193 -314
- package/dist/chunk-HQLSVUOR.cjs.map +1 -0
- package/dist/chunk-ISV6HFQE.cjs +40 -0
- package/dist/chunk-ISV6HFQE.cjs.map +1 -0
- package/dist/{chunk-3YSXPBEQ.js → chunk-TR2YNWZ5.js} +190 -313
- package/dist/chunk-TR2YNWZ5.js.map +1 -0
- package/dist/{chunk-ZLASWV4N.js → chunk-UKP4W6DI.js} +56 -145
- package/dist/chunk-UKP4W6DI.js.map +1 -0
- package/dist/{chunk-LPWEQCZV.cjs → chunk-YSL7KJNU.cjs} +21 -92
- package/dist/chunk-YSL7KJNU.cjs.map +1 -0
- package/dist/codegen/cli.cjs +38 -1
- package/dist/codegen/cli.cjs.map +1 -1
- package/dist/codegen/cli.js +38 -1
- package/dist/codegen/cli.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/connect.iife.global.js +66 -43
- package/dist/{detect-BHYg26_d.d.cts → detect-DVGaL2bH.d.cts} +39 -33
- package/dist/{detect-Bf8Q_0dK.d.ts → detect-DVGaL2bH.d.ts} +39 -33
- package/dist/host/index.cjs +17 -17
- package/dist/host/index.d.cts +11 -27
- package/dist/host/index.d.ts +11 -27
- package/dist/host/index.js +2 -2
- package/dist/index.cjs +52 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -31
- package/dist/index.d.ts +104 -31
- package/dist/index.js +34 -3
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +11 -10
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +49 -12
- package/dist/react/index.d.ts +49 -12
- package/dist/react/index.js +6 -5
- package/dist/react/index.js.map +1 -1
- package/dist/{server-QIYIJHD5.cjs → server-BR5MNFOB.cjs} +40 -29
- package/dist/server-BR5MNFOB.cjs.map +1 -0
- package/dist/{server-5N76YCWC.js → server-OOEEWAEG.js} +40 -30
- package/dist/server-OOEEWAEG.js.map +1 -0
- package/dist/synapse-runtime.iife.global.js +66 -43
- package/dist/synapse-ui.iife.global.js +4 -4
- package/dist/{types-uEO4VFJ2.d.cts → types-BxPfGHKO.d.cts} +48 -125
- package/dist/{types-uEO4VFJ2.d.ts → types-BxPfGHKO.d.ts} +48 -125
- package/dist/vite/index.cjs +77 -33
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.d.cts +5 -1
- package/dist/vite/index.d.ts +5 -1
- package/dist/vite/index.js +77 -33
- package/dist/vite/index.js.map +1 -1
- package/package.json +12 -6
- package/dist/chunk-3YSXPBEQ.js.map +0 -1
- package/dist/chunk-FB7GPBEM.cjs.map +0 -1
- package/dist/chunk-HKRTDGXN.js.map +0 -1
- package/dist/chunk-LPWEQCZV.cjs.map +0 -1
- package/dist/chunk-VDQZQDU3.cjs.map +0 -1
- package/dist/chunk-ZLASWV4N.js.map +0 -1
- package/dist/server-5N76YCWC.js.map +0 -1
- package/dist/server-QIYIJHD5.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
(function(){'use strict';
|
|
2
|
-
`),
|
|
1
|
+
(function(){'use strict';function v(e,n){if(!e)return null;let a=e.getElementById(n)?.textContent;if(!a)return null;try{return JSON.parse(a)??null}catch{return null}}var ce={"--color-background-primary":"#ffffff","--color-background-secondary":"#fafafa","--color-background-tertiary":"#f3f4f6","--color-text-primary":"#111827","--color-text-secondary":"#6b7280","--color-text-tertiary":"#9ca3af","--color-text-accent":"#2563eb","--nb-color-accent-foreground":"#ffffff","--color-border-primary":"#e5e7eb","--color-border-secondary":"#d1d5db","--color-ring-primary":"#2563eb","--nb-color-danger":"#dc2626","--nb-color-danger-foreground":"#ffffff","--nb-color-success":"#059669","--nb-color-warning":"#f59e0b","--nb-color-processing":"#7c3aed","--nb-color-processing-light":"#f3eeff","--nb-color-info-light":"#eef4ff"},de={"--color-background-primary":"#18181b","--color-background-secondary":"#27272a","--color-background-tertiary":"#2f2f34","--color-text-primary":"#fafafa","--color-text-secondary":"#a1a1aa","--color-text-tertiary":"#71717a","--color-text-accent":"#818cf8","--nb-color-accent-foreground":"#ffffff","--color-border-primary":"#3f3f46","--color-border-secondary":"#52525b","--color-ring-primary":"#818cf8","--nb-color-danger":"#f87171","--nb-color-danger-foreground":"#18181b","--nb-color-success":"#34d399","--nb-color-warning":"#fbbf24","--nb-color-processing":"#a78bfa","--nb-color-processing-light":"#2a2440","--nb-color-info-light":"#1e2a44"},le={light:ce,dark:de},ue="synapse-defaults",D="synapse-theme-defaults",U=new Set;function pe(){return typeof document<"u"&&typeof document.getElementById=="function"&&typeof document.createElement=="function"&&typeof document.head?.prepend=="function"}function fe(e){if(!pe())return;let n=Object.entries(le[e]).map(([s,l])=>` ${s}: ${l};`).join(`
|
|
2
|
+
`),o=`@layer ${ue} {
|
|
3
3
|
:root {
|
|
4
|
-
${
|
|
4
|
+
${n}
|
|
5
5
|
}
|
|
6
|
-
}`,
|
|
6
|
+
}`,a=document.getElementById(D);if(a){a.textContent!==o&&(a.textContent=o);return}let i=document.createElement("style");i.id=D,i.textContent=o,document.head.prepend(i);}function me(e,n){if(typeof document>"u")return;fe(e);let o={};if(n&&typeof n=="object")for(let[i,s]of Object.entries(n))typeof i=="string"&&typeof s=="string"&&(o[i]=s);let a=document.documentElement.style;if(typeof a.removeProperty=="function")for(let i of U)i in o||a.removeProperty(i);for(let[i,s]of Object.entries(o))a.setProperty(i,s);U=new Set(Object.keys(o));}function N(e,n){me(e,n);}function O(e){typeof document<"u"&&document.documentElement.setAttribute("data-theme",e.mode),N(e.mode,e.tokens);}var z="__mcp-host-fonts";function j(e){if(typeof document>"u"||document.getElementById(z))return;let n=document.createElement("style");n.id=z,n.textContent=e,document.head.appendChild(n);}function I(e){try{if(e?.matchMedia?.("(prefers-color-scheme: dark)").matches)return "dark"}catch{}return "light"}function C(e,n){return e==="light"||e==="dark"?e:n}var A=class extends Error{constructor(n,o){super(`"${n}" is not supported by the "${o}" host`),this.name="HostUnsupportedError";}},x=class extends Error{result;constructor(n,o){super(ge(o)??`tool "${n}" returned an error`),this.name="ToolCallError",this.result=o;}};function ge(e){let n=e?.content;if(Array.isArray(n))for(let o of n){let a=o;if(a?.type==="text"&&typeof a.text=="string"&&a.text!=="")return a.text}}function K(e){return e!=null&&typeof e=="object"&&e.isError===true}var w="synapse-ui-data",W="2026-01-26",F="ui/initialize",$="ui/notifications/initialized",G="ui/notifications/tool-result",Y="ui/notifications/host-context-changed",Z="ui/notifications/size-changed",q="ui/open-link",B="ui/message",Q="ui/resource-teardown",V="tools/call";function X(e,n){let o=n.dataElementId??w,a=null,i={mode:I(e),tokens:{}},s=new Set,l=false,d=null,p=u=>{if(l)return;let T=C(u.matches?"dark":"light",i.mode);if(T!==i.mode){i={mode:T,tokens:{}};for(let f of s)f(i);}};return {host:"generic",getData:()=>a,onData(){return ()=>{}},getTheme:()=>i,onTheme(u){return s.add(u),()=>s.delete(u)},async callTool(u){throw new A("callTool","generic")},sendPrompt(){},openLink(u){e.open(u,"_blank","noopener,noreferrer");},resize(){},capabilities(){return {pull:false,sendPrompt:false,openLink:true}},start(){a=v(e.document,o);try{d=e.matchMedia?.("(prefers-color-scheme: dark)")??null,d?.addEventListener?.("change",p);}catch{d=null;}},destroy(){l||(l=true,d?.removeEventListener?.("change",p),d=null,s.clear());}}}function J(e){let n=e?.styles?.css?.fonts;return typeof n=="string"&&n!==""?n:void 0}var ye=3e4;function ee(e,n){let o=n.dataElementId??w,a=n.autoResize!==false,i=null,s={mode:I(e),tokens:{}},l=new Set,d=new Set,p=false,u=false,T=1,f=new Map,S=-1,b=null,y=null,M=()=>e.parent??e;function _(t){M().postMessage(t,"*");}function R(t,r){_({jsonrpc:"2.0",method:t,params:r??{}});}function k(t,r){let c=T++;return new Promise((m,g)=>{let h=setTimeout(()=>{f.delete(c),g(new Error(`"${t}" timed out`));},ye);f.set(c,{resolve:m,reject:g,timer:h}),_({jsonrpc:"2.0",id:c,method:t,params:r??{}});})}function oe(t){if(t!=null){i=t;for(let r of l)r(t);}}function L(t){if(!t||typeof t!="object")return;let r=J(t);r!==void 0&&j(r);let{mode:c,tokens:m}=s,g=false;if(t.theme!=null){let P=C(t.theme,c);P!==c&&(c=P,g=true);}let h=t.styles;if(h?.variables&&typeof h.variables=="object"&&(m={...m,...h.variables},g=true),g){s={mode:c,tokens:m};for(let P of d)P(s);}}function E(t){if(p||!u)return;let r=typeof t=="number"?t:Math.ceil(e.document.body.scrollHeight);r!==S&&(S=r,R(Z,{height:r}));}function re(t){let r=Number(t.id),c=f.get(r);if(c)if(f.delete(r),clearTimeout(c.timer),t.error!=null){let m=t.error;c.reject(new Error(m.message??"request failed"));}else c.resolve(t.result);}function se(t,r){if(t===G){let c=r.structuredContent;oe(c??r);}else t===Y&&L(r);}function ie(t){t.method===Q&&_({jsonrpc:"2.0",id:t.id,result:{}});}let H=t=>{if(p||t.source&&t.source!==M())return;let r=t.data;!r||typeof r!="object"||r.jsonrpc!=="2.0"||(r.id!=null&&("result"in r||"error"in r)?re(r):typeof r.method=="string"&&(r.id!=null?ie(r):se(r.method,r.params??{})));};function ae(){y=()=>E(),e.addEventListener("resize",y),typeof e.ResizeObserver<"u"&&(b=new e.ResizeObserver(()=>E()),b.observe(e.document.body));}return {host:"mcp-apps",getData:()=>i,onData(t){return l.add(t),()=>l.delete(t)},getTheme:()=>s,onTheme(t){return d.add(t),()=>d.delete(t)},async callTool(t,r){return await k(V,{name:t,arguments:r??{}})},sendPrompt(t){k(B,{role:"user",content:[{type:"text",text:t}]}).catch(()=>{});},openLink(t){k(q,{url:t}).catch(()=>{});},resize(t){E(t);},capabilities(){return {pull:true,sendPrompt:true,openLink:true}},start(){e.addEventListener("message",H),i=v(e.document,o),a&&ae(),k(F,{appInfo:{name:n.name??"synapse-ui",version:n.version??"0.0.0"},appCapabilities:{availableDisplayModes:["inline"]},protocolVersion:W}).then(t=>{p||(L(t?.hostContext),R($,{}),u=true,E());}).catch(()=>{});},destroy(){if(!p){p=true,e.removeEventListener("message",H),y&&e.removeEventListener("resize",y),y=null,b?.disconnect(),b=null;for(let t of f.values())clearTimeout(t.timer),t.reject(new Error("adapter destroyed"));f.clear(),l.clear(),d.clear();}}}}function he(e){try{if(e.parent!=null&&e.parent!==e)return "mcp-apps"}catch{return "mcp-apps"}return "generic"}function Te(e,n,o){if(e==="mcp-apps")return ee(n,o);if(e==="generic")return X(n,o);throw new TypeError(`unknown host "${String(e)}": expected "mcp-apps" or "generic"`)}function te(e,n){let o=n.host??he(e);return Te(o,e,n)}function ne(e={}){let n=e.window??globalThis,o=te(n,e);O(o.getTheme());let a=o.onTheme(O);o.start();let i=false;return {data:()=>o.getData(),onData:s=>o.onData(s),theme:()=>o.getTheme(),onTheme:s=>o.onTheme(s),async callTool(s,l){let d=await o.callTool(s,l);if(K(d))throw new x(s,d);return d},sendPrompt:s=>o.sendPrompt(s),openLink:s=>o.openLink(s),resize:s=>o.resize(s),capabilities:()=>o.capabilities(),host:()=>o.host,destroy(){i||(i=true,a(),o.destroy());}}}globalThis.SynapseUI={connect:ne};})();
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { McpUiHostContext } from '@modelcontextprotocol/ext-apps';
|
|
1
|
+
import { McpUiHostContext, McpUiHostCapabilities } from '@modelcontextprotocol/ext-apps';
|
|
2
2
|
import { ReadResourceRequest, ReadResourceResult, Task } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shape of the `tasks` capability advertised in `appCapabilities` on the
|
|
6
|
-
* iframe side
|
|
6
|
+
* iframe side, and mirrored back by the host in `hostCapabilities.experimental`
|
|
7
|
+
* under the MCP Tasks extension identifier (see `readHostTasksCapability`).
|
|
7
8
|
*
|
|
8
9
|
* Matches the MCP 2025-11-25 tasks utility: empty objects (`{}`) are used
|
|
9
10
|
* as presence flags — NOT booleans — so future sub-fields can be added
|
|
@@ -46,13 +47,6 @@ interface CallToolAsTaskOptions {
|
|
|
46
47
|
* "unlimited" explicitly.
|
|
47
48
|
*/
|
|
48
49
|
ttl?: number;
|
|
49
|
-
/**
|
|
50
|
-
* Route the call through the internal-apps cross-server authz path
|
|
51
|
-
* (adds `params.server` set to this app's name). External apps MUST
|
|
52
|
-
* NOT pass this; spec doesn't touch it — it's a NimbleBrain-specific
|
|
53
|
-
* bridge convention mirroring `callTool`'s behavior.
|
|
54
|
-
*/
|
|
55
|
-
internal?: boolean;
|
|
56
50
|
}
|
|
57
51
|
/**
|
|
58
52
|
* Handle returned by `synapse.callToolAsTask`. Lifecycle mirrors the MCP
|
|
@@ -100,93 +94,9 @@ interface TaskHandle<TOutput = unknown> {
|
|
|
100
94
|
onStatus(cb: (task: Task) => void): () => void;
|
|
101
95
|
}
|
|
102
96
|
|
|
103
|
-
/**
|
|
104
|
-
* One `@font-face` a host asks the app to load.
|
|
105
|
-
*
|
|
106
|
-
* The token contract can name a font (`--font-sans: 'Hanken Grotesk', system-ui`)
|
|
107
|
-
* but cannot *load* one — a CSS custom property carries a family name, never the
|
|
108
|
-
* `@font-face` rule behind it. An app iframe is its own document and inherits no
|
|
109
|
-
* `@font-face` from the host page, so a host that only sends tokens is naming a
|
|
110
|
-
* family the app has no way to render. This descriptor is the missing half.
|
|
111
|
-
*
|
|
112
|
-
* The SDK ships **no font data** — it applies what the host sends. A host that
|
|
113
|
-
* sends none leaves the web-safe fallbacks in `ui/tokens.ts` in force.
|
|
114
|
-
*/
|
|
115
|
-
interface FontFaceDescriptor {
|
|
116
|
-
/** Family name, matching the one used in the host's `--font-*` token value. */
|
|
117
|
-
family: string;
|
|
118
|
-
/**
|
|
119
|
-
* CSS `src` descriptor. Local and absolute URLs work identically —
|
|
120
|
-
* `url('/fonts/x.woff2') format('woff2')` or `url('https://cdn.example/x.woff2')`.
|
|
121
|
-
* Whatever origin this names must satisfy the app iframe's `font-src` CSP.
|
|
122
|
-
*/
|
|
123
|
-
src: string;
|
|
124
|
-
/** `font-weight` descriptor — a single weight (`400`) or a variable range (`400 700`). */
|
|
125
|
-
weight?: string;
|
|
126
|
-
/** `font-style` descriptor (`normal`, `italic`, …). */
|
|
127
|
-
style?: string;
|
|
128
|
-
/** `font-display` descriptor. Defaults to `swap` so text paints in the fallback first. */
|
|
129
|
-
display?: FontDisplayValue;
|
|
130
|
-
}
|
|
131
|
-
/** The CSS `font-display` values. Closed set — anything else is ignored. */
|
|
132
|
-
type FontDisplayValue = "auto" | "block" | "swap" | "fallback" | "optional";
|
|
133
97
|
interface Theme {
|
|
134
98
|
mode: "light" | "dark";
|
|
135
99
|
tokens: Record<string, string>;
|
|
136
|
-
/**
|
|
137
|
-
* Font faces the host wants loaded into the app document. Optional — a host
|
|
138
|
-
* that omits them leaves the web-safe fallbacks in force. Arrives over the
|
|
139
|
-
* wire as the `synapse/fontFaces` host-context extension.
|
|
140
|
-
*/
|
|
141
|
-
fontFaces?: FontFaceDescriptor[];
|
|
142
|
-
}
|
|
143
|
-
interface DataChangedEvent {
|
|
144
|
-
source: "agent";
|
|
145
|
-
server: string;
|
|
146
|
-
tool: string;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Built-in action types that Synapse handles natively.
|
|
150
|
-
*
|
|
151
|
-
* - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
|
|
152
|
-
* - `notify` — display a transient message (toast/banner)
|
|
153
|
-
* - `refresh` — force a full data refresh (heavier than datachanged)
|
|
154
|
-
* - `confirm` — request user confirmation before the agent proceeds
|
|
155
|
-
*
|
|
156
|
-
* Apps may also receive custom string types for domain-specific actions.
|
|
157
|
-
*/
|
|
158
|
-
type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
|
|
159
|
-
/**
|
|
160
|
-
* A typed, declarative action sent from the agent/server to the UI.
|
|
161
|
-
*
|
|
162
|
-
* Actions are deterministic side effects of tool execution — the tool decides
|
|
163
|
-
* what action to emit, not the LLM. The UI decides how to handle it.
|
|
164
|
-
*
|
|
165
|
-
* This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
|
|
166
|
-
*/
|
|
167
|
-
interface AgentAction<TPayload = Record<string, unknown>> {
|
|
168
|
-
/** Discriminator — a BuiltinActionType or custom string. */
|
|
169
|
-
type: BuiltinActionType | (string & {});
|
|
170
|
-
/** Typed payload — shape depends on `type`. */
|
|
171
|
-
payload: TPayload;
|
|
172
|
-
/** If true, the UI should confirm with the user before executing. */
|
|
173
|
-
requiresConfirmation?: boolean;
|
|
174
|
-
/** Human-readable label for confirmation dialogs or logs. */
|
|
175
|
-
label?: string;
|
|
176
|
-
}
|
|
177
|
-
/** Payload for the built-in "navigate" action. */
|
|
178
|
-
interface NavigatePayload {
|
|
179
|
-
/** Entity type (e.g., "board", "document", "task"). */
|
|
180
|
-
entity: string;
|
|
181
|
-
/** Entity ID to select/focus. */
|
|
182
|
-
id: string;
|
|
183
|
-
/** Optional sub-view or section within the entity. */
|
|
184
|
-
view?: string;
|
|
185
|
-
}
|
|
186
|
-
/** Payload for the built-in "notify" action. */
|
|
187
|
-
interface NotifyPayload {
|
|
188
|
-
message: string;
|
|
189
|
-
level?: "info" | "success" | "warning" | "error";
|
|
190
100
|
}
|
|
191
101
|
interface ToolCallResult<T = unknown> {
|
|
192
102
|
data: T;
|
|
@@ -254,11 +164,6 @@ interface ToolDefinition {
|
|
|
254
164
|
inputSchema: Record<string, unknown>;
|
|
255
165
|
outputSchema?: Record<string, unknown>;
|
|
256
166
|
}
|
|
257
|
-
interface HostInfo {
|
|
258
|
-
isNimbleBrain: boolean;
|
|
259
|
-
serverName: string;
|
|
260
|
-
protocolVersion: string;
|
|
261
|
-
}
|
|
262
167
|
interface ConnectOptions {
|
|
263
168
|
/** App name — must match the bundle name registered with the host. */
|
|
264
169
|
name: string;
|
|
@@ -266,12 +171,6 @@ interface ConnectOptions {
|
|
|
266
171
|
version: string;
|
|
267
172
|
/** Track the document height and re-send `size-changed` as it moves. */
|
|
268
173
|
autoResize?: boolean;
|
|
269
|
-
/**
|
|
270
|
-
* Mark as an internal NimbleBrain app. Enables cross-server tool calls:
|
|
271
|
-
* `callTool` carries a `server` param so the host can route the call to a
|
|
272
|
-
* sibling server. External apps MUST NOT set this.
|
|
273
|
-
*/
|
|
274
|
-
internal?: boolean;
|
|
275
174
|
/**
|
|
276
175
|
* Forward keyboard shortcuts from this iframe up to the host, so the host's
|
|
277
176
|
* own shortcuts still fire while focus is inside the app.
|
|
@@ -280,8 +179,8 @@ interface ConnectOptions {
|
|
|
280
179
|
* the clipboard keys the browser must handle itself); an array forwards
|
|
281
180
|
* exactly the listed combos. Absent means no forwarding.
|
|
282
181
|
*
|
|
283
|
-
*
|
|
284
|
-
* off everywhere else — a `preventDefault` on a host that does nothing with
|
|
182
|
+
* Forwarding is on only where the host declares `ai.nimblebrain/keydown`, and
|
|
183
|
+
* stays off everywhere else — a `preventDefault` on a host that does nothing with
|
|
285
184
|
* the key would swallow it for no one's benefit.
|
|
286
185
|
*/
|
|
287
186
|
forwardKeys?: boolean | KeyForwardConfig[];
|
|
@@ -300,25 +199,15 @@ interface ToolResultData {
|
|
|
300
199
|
structuredContent: unknown;
|
|
301
200
|
raw: Record<string, unknown>;
|
|
302
201
|
}
|
|
303
|
-
/** Per-call overrides for {@link App.callTool}. */
|
|
304
|
-
interface CallToolOptions {
|
|
305
|
-
/**
|
|
306
|
-
* Route the call to a sibling MCP server rather than the app's own.
|
|
307
|
-
* Internal apps only — the host rejects it otherwise. Defaults to this
|
|
308
|
-
* app's name when `connect({ internal: true })` was used, which is what
|
|
309
|
-
* makes a plain `callTool` work for an internal app.
|
|
310
|
-
*/
|
|
311
|
-
server?: string;
|
|
312
|
-
}
|
|
313
202
|
/** Known short event names for {@link App.on}. */
|
|
314
|
-
type AppEventName = "tool-result" | "tool-input" | "tool-input-partial" | "tool-cancelled" | "theme-changed" | "host-context-changed" | "
|
|
203
|
+
type AppEventName = "tool-result" | "tool-input" | "tool-input-partial" | "tool-cancelled" | "theme-changed" | "host-context-changed" | "teardown";
|
|
315
204
|
/**
|
|
316
205
|
* A connected app — what `connect()` resolves to, and the only runtime object
|
|
317
206
|
* this SDK hands out.
|
|
318
207
|
*
|
|
319
208
|
* Deliberately small: it carries the ext-apps spec surface plus the state the
|
|
320
209
|
* handshake established. NimbleBrain's own extensions (the file picker,
|
|
321
|
-
* `action
|
|
210
|
+
* `action`), `downloadFile` and the MCP tasks utility are composable functions
|
|
322
211
|
* over this object rather than more methods on it.
|
|
323
212
|
*/
|
|
324
213
|
interface App {
|
|
@@ -339,7 +228,20 @@ interface App {
|
|
|
339
228
|
* host-specific fields as optional; another host will not send them.
|
|
340
229
|
*/
|
|
341
230
|
readonly hostContext: McpUiHostContext;
|
|
342
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* What the host declared it supports, from the `ui/initialize` result. Every
|
|
233
|
+
* method here and every helper beside it checks this before it sends, and
|
|
234
|
+
* degrades in a documented way when the capability is absent. Read it to
|
|
235
|
+
* decide what to offer at all, rather than to find out from a no-op or an
|
|
236
|
+
* exception. NimbleBrain extensions are declared under `experimental`; see
|
|
237
|
+
* `hostSupports`.
|
|
238
|
+
*/
|
|
239
|
+
readonly hostCapabilities: McpUiHostCapabilities;
|
|
240
|
+
/**
|
|
241
|
+
* True when the host identified itself as NimbleBrain in the handshake.
|
|
242
|
+
* Identity, not capability: nothing is gated on it except the NimbleBrain
|
|
243
|
+
* chat context on `sendMessage` (`_meta["ai.nimblebrain/context"]`), which other hosts ignore.
|
|
244
|
+
*/
|
|
343
245
|
readonly isNimbleBrainHost: boolean;
|
|
344
246
|
/** True after `destroy()` has been called. */
|
|
345
247
|
readonly destroyed: boolean;
|
|
@@ -359,11 +261,14 @@ interface App {
|
|
|
359
261
|
* notification exactly as sent, subscribe to the wire method instead:
|
|
360
262
|
* `on("ui/notifications/host-context-changed", …)`. */
|
|
361
263
|
on(event: "host-context-changed", handler: (ctx: McpUiHostContext) => void): () => void;
|
|
362
|
-
on(event: "data-changed", handler: (event: DataChangedEvent) => void): () => void;
|
|
363
|
-
on(event: "action", handler: (action: AgentAction) => void): () => void;
|
|
364
264
|
on(event: "teardown", handler: () => void): () => void;
|
|
365
265
|
on(event: string, handler: (params: any) => void): () => void;
|
|
266
|
+
/** Report the frame's size (`ui/notifications/size-changed`). Every host accepts it. */
|
|
366
267
|
resize(width?: number, height?: number): void;
|
|
268
|
+
/**
|
|
269
|
+
* Open a URL through the host (`ui/open-link`). Without `openLinks`, or when
|
|
270
|
+
* the host refuses, opens it with `window.open` instead.
|
|
271
|
+
*/
|
|
367
272
|
openLink(url: string): void;
|
|
368
273
|
/**
|
|
369
274
|
* Push the app's visible state to the agent (ext-apps
|
|
@@ -371,17 +276,35 @@ interface App {
|
|
|
371
276
|
* `state` rides along as `structuredContent` for tools that need the ids.
|
|
372
277
|
*
|
|
373
278
|
* Sends immediately. Callers that push on every keystroke or selection
|
|
374
|
-
* change want `useModelContext`, which debounces.
|
|
279
|
+
* change want `useModelContext`, which debounces. A no-op when the host did
|
|
280
|
+
* not declare `updateModelContext`.
|
|
375
281
|
*/
|
|
376
282
|
updateModelContext(state: Record<string, unknown>, summary?: string): void;
|
|
377
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Call a tool on this app's own MCP server.
|
|
285
|
+
*
|
|
286
|
+
* An app reaches its own server and nothing else. A host scopes every call
|
|
287
|
+
* to the server that mounted the app, so there is no target to name and no
|
|
288
|
+
* option to pass — cross-source work belongs to the agent, which can call
|
|
289
|
+
* two servers and hand one's result to the other.
|
|
290
|
+
*
|
|
291
|
+
* Rejects with `HostCapabilityError`, without sending, when the host did not
|
|
292
|
+
* declare `serverTools`.
|
|
293
|
+
*/
|
|
294
|
+
callTool<TOutput = unknown>(name: string, args?: Record<string, unknown>): Promise<ToolCallResult<TOutput>>;
|
|
378
295
|
/**
|
|
379
296
|
* Read an MCP resource from the originating server via the host bridge
|
|
380
297
|
* (ext-apps `resources/read`). Named to mirror the ext-apps spec's
|
|
381
298
|
* `App.readServerResource`.
|
|
299
|
+
*
|
|
300
|
+
* Rejects with `HostCapabilityError`, without sending, when the host did not
|
|
301
|
+
* declare `serverResources`.
|
|
382
302
|
*/
|
|
383
303
|
readServerResource(params: ReadResourceRequest["params"]): Promise<ReadResourceResult>;
|
|
384
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* Send a user message into the agent conversation (ext-apps `ui/message`).
|
|
306
|
+
* A no-op when the host did not declare `message`.
|
|
307
|
+
*/
|
|
385
308
|
sendMessage(text: string, context?: {
|
|
386
309
|
action?: string;
|
|
387
310
|
entity?: string;
|
|
@@ -389,4 +312,4 @@ interface App {
|
|
|
389
312
|
destroy(): void;
|
|
390
313
|
}
|
|
391
314
|
|
|
392
|
-
export type { App as A,
|
|
315
|
+
export type { App as A, ConnectOptions as C, Dimensions as D, FileResult as F, KeyForwardConfig as K, ModelContext as M, RequestFileOptions as R, TaskHandle as T, CallToolAsTaskOptions as a, ToolCallResult as b, Theme as c, ToolResultData as d, ToolDefinition as e, AppEventName as f, TasksCapability as g };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { McpUiHostContext } from '@modelcontextprotocol/ext-apps';
|
|
1
|
+
import { McpUiHostContext, McpUiHostCapabilities } from '@modelcontextprotocol/ext-apps';
|
|
2
2
|
import { ReadResourceRequest, ReadResourceResult, Task } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shape of the `tasks` capability advertised in `appCapabilities` on the
|
|
6
|
-
* iframe side
|
|
6
|
+
* iframe side, and mirrored back by the host in `hostCapabilities.experimental`
|
|
7
|
+
* under the MCP Tasks extension identifier (see `readHostTasksCapability`).
|
|
7
8
|
*
|
|
8
9
|
* Matches the MCP 2025-11-25 tasks utility: empty objects (`{}`) are used
|
|
9
10
|
* as presence flags — NOT booleans — so future sub-fields can be added
|
|
@@ -46,13 +47,6 @@ interface CallToolAsTaskOptions {
|
|
|
46
47
|
* "unlimited" explicitly.
|
|
47
48
|
*/
|
|
48
49
|
ttl?: number;
|
|
49
|
-
/**
|
|
50
|
-
* Route the call through the internal-apps cross-server authz path
|
|
51
|
-
* (adds `params.server` set to this app's name). External apps MUST
|
|
52
|
-
* NOT pass this; spec doesn't touch it — it's a NimbleBrain-specific
|
|
53
|
-
* bridge convention mirroring `callTool`'s behavior.
|
|
54
|
-
*/
|
|
55
|
-
internal?: boolean;
|
|
56
50
|
}
|
|
57
51
|
/**
|
|
58
52
|
* Handle returned by `synapse.callToolAsTask`. Lifecycle mirrors the MCP
|
|
@@ -100,93 +94,9 @@ interface TaskHandle<TOutput = unknown> {
|
|
|
100
94
|
onStatus(cb: (task: Task) => void): () => void;
|
|
101
95
|
}
|
|
102
96
|
|
|
103
|
-
/**
|
|
104
|
-
* One `@font-face` a host asks the app to load.
|
|
105
|
-
*
|
|
106
|
-
* The token contract can name a font (`--font-sans: 'Hanken Grotesk', system-ui`)
|
|
107
|
-
* but cannot *load* one — a CSS custom property carries a family name, never the
|
|
108
|
-
* `@font-face` rule behind it. An app iframe is its own document and inherits no
|
|
109
|
-
* `@font-face` from the host page, so a host that only sends tokens is naming a
|
|
110
|
-
* family the app has no way to render. This descriptor is the missing half.
|
|
111
|
-
*
|
|
112
|
-
* The SDK ships **no font data** — it applies what the host sends. A host that
|
|
113
|
-
* sends none leaves the web-safe fallbacks in `ui/tokens.ts` in force.
|
|
114
|
-
*/
|
|
115
|
-
interface FontFaceDescriptor {
|
|
116
|
-
/** Family name, matching the one used in the host's `--font-*` token value. */
|
|
117
|
-
family: string;
|
|
118
|
-
/**
|
|
119
|
-
* CSS `src` descriptor. Local and absolute URLs work identically —
|
|
120
|
-
* `url('/fonts/x.woff2') format('woff2')` or `url('https://cdn.example/x.woff2')`.
|
|
121
|
-
* Whatever origin this names must satisfy the app iframe's `font-src` CSP.
|
|
122
|
-
*/
|
|
123
|
-
src: string;
|
|
124
|
-
/** `font-weight` descriptor — a single weight (`400`) or a variable range (`400 700`). */
|
|
125
|
-
weight?: string;
|
|
126
|
-
/** `font-style` descriptor (`normal`, `italic`, …). */
|
|
127
|
-
style?: string;
|
|
128
|
-
/** `font-display` descriptor. Defaults to `swap` so text paints in the fallback first. */
|
|
129
|
-
display?: FontDisplayValue;
|
|
130
|
-
}
|
|
131
|
-
/** The CSS `font-display` values. Closed set — anything else is ignored. */
|
|
132
|
-
type FontDisplayValue = "auto" | "block" | "swap" | "fallback" | "optional";
|
|
133
97
|
interface Theme {
|
|
134
98
|
mode: "light" | "dark";
|
|
135
99
|
tokens: Record<string, string>;
|
|
136
|
-
/**
|
|
137
|
-
* Font faces the host wants loaded into the app document. Optional — a host
|
|
138
|
-
* that omits them leaves the web-safe fallbacks in force. Arrives over the
|
|
139
|
-
* wire as the `synapse/fontFaces` host-context extension.
|
|
140
|
-
*/
|
|
141
|
-
fontFaces?: FontFaceDescriptor[];
|
|
142
|
-
}
|
|
143
|
-
interface DataChangedEvent {
|
|
144
|
-
source: "agent";
|
|
145
|
-
server: string;
|
|
146
|
-
tool: string;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Built-in action types that Synapse handles natively.
|
|
150
|
-
*
|
|
151
|
-
* - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
|
|
152
|
-
* - `notify` — display a transient message (toast/banner)
|
|
153
|
-
* - `refresh` — force a full data refresh (heavier than datachanged)
|
|
154
|
-
* - `confirm` — request user confirmation before the agent proceeds
|
|
155
|
-
*
|
|
156
|
-
* Apps may also receive custom string types for domain-specific actions.
|
|
157
|
-
*/
|
|
158
|
-
type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
|
|
159
|
-
/**
|
|
160
|
-
* A typed, declarative action sent from the agent/server to the UI.
|
|
161
|
-
*
|
|
162
|
-
* Actions are deterministic side effects of tool execution — the tool decides
|
|
163
|
-
* what action to emit, not the LLM. The UI decides how to handle it.
|
|
164
|
-
*
|
|
165
|
-
* This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
|
|
166
|
-
*/
|
|
167
|
-
interface AgentAction<TPayload = Record<string, unknown>> {
|
|
168
|
-
/** Discriminator — a BuiltinActionType or custom string. */
|
|
169
|
-
type: BuiltinActionType | (string & {});
|
|
170
|
-
/** Typed payload — shape depends on `type`. */
|
|
171
|
-
payload: TPayload;
|
|
172
|
-
/** If true, the UI should confirm with the user before executing. */
|
|
173
|
-
requiresConfirmation?: boolean;
|
|
174
|
-
/** Human-readable label for confirmation dialogs or logs. */
|
|
175
|
-
label?: string;
|
|
176
|
-
}
|
|
177
|
-
/** Payload for the built-in "navigate" action. */
|
|
178
|
-
interface NavigatePayload {
|
|
179
|
-
/** Entity type (e.g., "board", "document", "task"). */
|
|
180
|
-
entity: string;
|
|
181
|
-
/** Entity ID to select/focus. */
|
|
182
|
-
id: string;
|
|
183
|
-
/** Optional sub-view or section within the entity. */
|
|
184
|
-
view?: string;
|
|
185
|
-
}
|
|
186
|
-
/** Payload for the built-in "notify" action. */
|
|
187
|
-
interface NotifyPayload {
|
|
188
|
-
message: string;
|
|
189
|
-
level?: "info" | "success" | "warning" | "error";
|
|
190
100
|
}
|
|
191
101
|
interface ToolCallResult<T = unknown> {
|
|
192
102
|
data: T;
|
|
@@ -254,11 +164,6 @@ interface ToolDefinition {
|
|
|
254
164
|
inputSchema: Record<string, unknown>;
|
|
255
165
|
outputSchema?: Record<string, unknown>;
|
|
256
166
|
}
|
|
257
|
-
interface HostInfo {
|
|
258
|
-
isNimbleBrain: boolean;
|
|
259
|
-
serverName: string;
|
|
260
|
-
protocolVersion: string;
|
|
261
|
-
}
|
|
262
167
|
interface ConnectOptions {
|
|
263
168
|
/** App name — must match the bundle name registered with the host. */
|
|
264
169
|
name: string;
|
|
@@ -266,12 +171,6 @@ interface ConnectOptions {
|
|
|
266
171
|
version: string;
|
|
267
172
|
/** Track the document height and re-send `size-changed` as it moves. */
|
|
268
173
|
autoResize?: boolean;
|
|
269
|
-
/**
|
|
270
|
-
* Mark as an internal NimbleBrain app. Enables cross-server tool calls:
|
|
271
|
-
* `callTool` carries a `server` param so the host can route the call to a
|
|
272
|
-
* sibling server. External apps MUST NOT set this.
|
|
273
|
-
*/
|
|
274
|
-
internal?: boolean;
|
|
275
174
|
/**
|
|
276
175
|
* Forward keyboard shortcuts from this iframe up to the host, so the host's
|
|
277
176
|
* own shortcuts still fire while focus is inside the app.
|
|
@@ -280,8 +179,8 @@ interface ConnectOptions {
|
|
|
280
179
|
* the clipboard keys the browser must handle itself); an array forwards
|
|
281
180
|
* exactly the listed combos. Absent means no forwarding.
|
|
282
181
|
*
|
|
283
|
-
*
|
|
284
|
-
* off everywhere else — a `preventDefault` on a host that does nothing with
|
|
182
|
+
* Forwarding is on only where the host declares `ai.nimblebrain/keydown`, and
|
|
183
|
+
* stays off everywhere else — a `preventDefault` on a host that does nothing with
|
|
285
184
|
* the key would swallow it for no one's benefit.
|
|
286
185
|
*/
|
|
287
186
|
forwardKeys?: boolean | KeyForwardConfig[];
|
|
@@ -300,25 +199,15 @@ interface ToolResultData {
|
|
|
300
199
|
structuredContent: unknown;
|
|
301
200
|
raw: Record<string, unknown>;
|
|
302
201
|
}
|
|
303
|
-
/** Per-call overrides for {@link App.callTool}. */
|
|
304
|
-
interface CallToolOptions {
|
|
305
|
-
/**
|
|
306
|
-
* Route the call to a sibling MCP server rather than the app's own.
|
|
307
|
-
* Internal apps only — the host rejects it otherwise. Defaults to this
|
|
308
|
-
* app's name when `connect({ internal: true })` was used, which is what
|
|
309
|
-
* makes a plain `callTool` work for an internal app.
|
|
310
|
-
*/
|
|
311
|
-
server?: string;
|
|
312
|
-
}
|
|
313
202
|
/** Known short event names for {@link App.on}. */
|
|
314
|
-
type AppEventName = "tool-result" | "tool-input" | "tool-input-partial" | "tool-cancelled" | "theme-changed" | "host-context-changed" | "
|
|
203
|
+
type AppEventName = "tool-result" | "tool-input" | "tool-input-partial" | "tool-cancelled" | "theme-changed" | "host-context-changed" | "teardown";
|
|
315
204
|
/**
|
|
316
205
|
* A connected app — what `connect()` resolves to, and the only runtime object
|
|
317
206
|
* this SDK hands out.
|
|
318
207
|
*
|
|
319
208
|
* Deliberately small: it carries the ext-apps spec surface plus the state the
|
|
320
209
|
* handshake established. NimbleBrain's own extensions (the file picker,
|
|
321
|
-
* `action
|
|
210
|
+
* `action`), `downloadFile` and the MCP tasks utility are composable functions
|
|
322
211
|
* over this object rather than more methods on it.
|
|
323
212
|
*/
|
|
324
213
|
interface App {
|
|
@@ -339,7 +228,20 @@ interface App {
|
|
|
339
228
|
* host-specific fields as optional; another host will not send them.
|
|
340
229
|
*/
|
|
341
230
|
readonly hostContext: McpUiHostContext;
|
|
342
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* What the host declared it supports, from the `ui/initialize` result. Every
|
|
233
|
+
* method here and every helper beside it checks this before it sends, and
|
|
234
|
+
* degrades in a documented way when the capability is absent. Read it to
|
|
235
|
+
* decide what to offer at all, rather than to find out from a no-op or an
|
|
236
|
+
* exception. NimbleBrain extensions are declared under `experimental`; see
|
|
237
|
+
* `hostSupports`.
|
|
238
|
+
*/
|
|
239
|
+
readonly hostCapabilities: McpUiHostCapabilities;
|
|
240
|
+
/**
|
|
241
|
+
* True when the host identified itself as NimbleBrain in the handshake.
|
|
242
|
+
* Identity, not capability: nothing is gated on it except the NimbleBrain
|
|
243
|
+
* chat context on `sendMessage` (`_meta["ai.nimblebrain/context"]`), which other hosts ignore.
|
|
244
|
+
*/
|
|
343
245
|
readonly isNimbleBrainHost: boolean;
|
|
344
246
|
/** True after `destroy()` has been called. */
|
|
345
247
|
readonly destroyed: boolean;
|
|
@@ -359,11 +261,14 @@ interface App {
|
|
|
359
261
|
* notification exactly as sent, subscribe to the wire method instead:
|
|
360
262
|
* `on("ui/notifications/host-context-changed", …)`. */
|
|
361
263
|
on(event: "host-context-changed", handler: (ctx: McpUiHostContext) => void): () => void;
|
|
362
|
-
on(event: "data-changed", handler: (event: DataChangedEvent) => void): () => void;
|
|
363
|
-
on(event: "action", handler: (action: AgentAction) => void): () => void;
|
|
364
264
|
on(event: "teardown", handler: () => void): () => void;
|
|
365
265
|
on(event: string, handler: (params: any) => void): () => void;
|
|
266
|
+
/** Report the frame's size (`ui/notifications/size-changed`). Every host accepts it. */
|
|
366
267
|
resize(width?: number, height?: number): void;
|
|
268
|
+
/**
|
|
269
|
+
* Open a URL through the host (`ui/open-link`). Without `openLinks`, or when
|
|
270
|
+
* the host refuses, opens it with `window.open` instead.
|
|
271
|
+
*/
|
|
367
272
|
openLink(url: string): void;
|
|
368
273
|
/**
|
|
369
274
|
* Push the app's visible state to the agent (ext-apps
|
|
@@ -371,17 +276,35 @@ interface App {
|
|
|
371
276
|
* `state` rides along as `structuredContent` for tools that need the ids.
|
|
372
277
|
*
|
|
373
278
|
* Sends immediately. Callers that push on every keystroke or selection
|
|
374
|
-
* change want `useModelContext`, which debounces.
|
|
279
|
+
* change want `useModelContext`, which debounces. A no-op when the host did
|
|
280
|
+
* not declare `updateModelContext`.
|
|
375
281
|
*/
|
|
376
282
|
updateModelContext(state: Record<string, unknown>, summary?: string): void;
|
|
377
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Call a tool on this app's own MCP server.
|
|
285
|
+
*
|
|
286
|
+
* An app reaches its own server and nothing else. A host scopes every call
|
|
287
|
+
* to the server that mounted the app, so there is no target to name and no
|
|
288
|
+
* option to pass — cross-source work belongs to the agent, which can call
|
|
289
|
+
* two servers and hand one's result to the other.
|
|
290
|
+
*
|
|
291
|
+
* Rejects with `HostCapabilityError`, without sending, when the host did not
|
|
292
|
+
* declare `serverTools`.
|
|
293
|
+
*/
|
|
294
|
+
callTool<TOutput = unknown>(name: string, args?: Record<string, unknown>): Promise<ToolCallResult<TOutput>>;
|
|
378
295
|
/**
|
|
379
296
|
* Read an MCP resource from the originating server via the host bridge
|
|
380
297
|
* (ext-apps `resources/read`). Named to mirror the ext-apps spec's
|
|
381
298
|
* `App.readServerResource`.
|
|
299
|
+
*
|
|
300
|
+
* Rejects with `HostCapabilityError`, without sending, when the host did not
|
|
301
|
+
* declare `serverResources`.
|
|
382
302
|
*/
|
|
383
303
|
readServerResource(params: ReadResourceRequest["params"]): Promise<ReadResourceResult>;
|
|
384
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* Send a user message into the agent conversation (ext-apps `ui/message`).
|
|
306
|
+
* A no-op when the host did not declare `message`.
|
|
307
|
+
*/
|
|
385
308
|
sendMessage(text: string, context?: {
|
|
386
309
|
action?: string;
|
|
387
310
|
entity?: string;
|
|
@@ -389,4 +312,4 @@ interface App {
|
|
|
389
312
|
destroy(): void;
|
|
390
313
|
}
|
|
391
314
|
|
|
392
|
-
export type { App as A,
|
|
315
|
+
export type { App as A, ConnectOptions as C, Dimensions as D, FileResult as F, KeyForwardConfig as K, ModelContext as M, RequestFileOptions as R, TaskHandle as T, CallToolAsTaskOptions as a, ToolCallResult as b, Theme as c, ToolResultData as d, ToolDefinition as e, AppEventName as f, TasksCapability as g };
|