@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.
Files changed (69) hide show
  1. package/README.md +32 -37
  2. package/dist/check-JEM5PN64.cjs +435 -0
  3. package/dist/check-JEM5PN64.cjs.map +1 -0
  4. package/dist/check-YDZUQT6A.js +429 -0
  5. package/dist/check-YDZUQT6A.js.map +1 -0
  6. package/dist/{chunk-VDQZQDU3.cjs → chunk-BNF352SA.cjs} +56 -145
  7. package/dist/chunk-BNF352SA.cjs.map +1 -0
  8. package/dist/{chunk-HKRTDGXN.js → chunk-DJS2FKBN.js} +21 -89
  9. package/dist/chunk-DJS2FKBN.js.map +1 -0
  10. package/dist/chunk-H3INUKA3.js +32 -0
  11. package/dist/chunk-H3INUKA3.js.map +1 -0
  12. package/dist/{chunk-FB7GPBEM.cjs → chunk-HQLSVUOR.cjs} +193 -314
  13. package/dist/chunk-HQLSVUOR.cjs.map +1 -0
  14. package/dist/chunk-ISV6HFQE.cjs +40 -0
  15. package/dist/chunk-ISV6HFQE.cjs.map +1 -0
  16. package/dist/{chunk-3YSXPBEQ.js → chunk-TR2YNWZ5.js} +190 -313
  17. package/dist/chunk-TR2YNWZ5.js.map +1 -0
  18. package/dist/{chunk-ZLASWV4N.js → chunk-UKP4W6DI.js} +56 -145
  19. package/dist/chunk-UKP4W6DI.js.map +1 -0
  20. package/dist/{chunk-LPWEQCZV.cjs → chunk-YSL7KJNU.cjs} +21 -92
  21. package/dist/chunk-YSL7KJNU.cjs.map +1 -0
  22. package/dist/codegen/cli.cjs +38 -1
  23. package/dist/codegen/cli.cjs.map +1 -1
  24. package/dist/codegen/cli.js +38 -1
  25. package/dist/codegen/cli.js.map +1 -1
  26. package/dist/codegen/index.d.cts +1 -1
  27. package/dist/codegen/index.d.ts +1 -1
  28. package/dist/connect.iife.global.js +66 -43
  29. package/dist/{detect-BHYg26_d.d.cts → detect-DVGaL2bH.d.cts} +39 -33
  30. package/dist/{detect-Bf8Q_0dK.d.ts → detect-DVGaL2bH.d.ts} +39 -33
  31. package/dist/host/index.cjs +17 -17
  32. package/dist/host/index.d.cts +11 -27
  33. package/dist/host/index.d.ts +11 -27
  34. package/dist/host/index.js +2 -2
  35. package/dist/index.cjs +52 -17
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +104 -31
  38. package/dist/index.d.ts +104 -31
  39. package/dist/index.js +34 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/react/index.cjs +11 -10
  42. package/dist/react/index.cjs.map +1 -1
  43. package/dist/react/index.d.cts +49 -12
  44. package/dist/react/index.d.ts +49 -12
  45. package/dist/react/index.js +6 -5
  46. package/dist/react/index.js.map +1 -1
  47. package/dist/{server-QIYIJHD5.cjs → server-BR5MNFOB.cjs} +40 -29
  48. package/dist/server-BR5MNFOB.cjs.map +1 -0
  49. package/dist/{server-5N76YCWC.js → server-OOEEWAEG.js} +40 -30
  50. package/dist/server-OOEEWAEG.js.map +1 -0
  51. package/dist/synapse-runtime.iife.global.js +66 -43
  52. package/dist/synapse-ui.iife.global.js +4 -4
  53. package/dist/{types-uEO4VFJ2.d.cts → types-BxPfGHKO.d.cts} +48 -125
  54. package/dist/{types-uEO4VFJ2.d.ts → types-BxPfGHKO.d.ts} +48 -125
  55. package/dist/vite/index.cjs +77 -33
  56. package/dist/vite/index.cjs.map +1 -1
  57. package/dist/vite/index.d.cts +5 -1
  58. package/dist/vite/index.d.ts +5 -1
  59. package/dist/vite/index.js +77 -33
  60. package/dist/vite/index.js.map +1 -1
  61. package/package.json +12 -6
  62. package/dist/chunk-3YSXPBEQ.js.map +0 -1
  63. package/dist/chunk-FB7GPBEM.cjs.map +0 -1
  64. package/dist/chunk-HKRTDGXN.js.map +0 -1
  65. package/dist/chunk-LPWEQCZV.cjs.map +0 -1
  66. package/dist/chunk-VDQZQDU3.cjs.map +0 -1
  67. package/dist/chunk-ZLASWV4N.js.map +0 -1
  68. package/dist/server-5N76YCWC.js.map +0 -1
  69. package/dist/server-QIYIJHD5.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
- (function(){'use strict';var be=new Set(["auto","block","swap","fallback","optional"]),ke={"--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"},Pe={"--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"},Ee={light:ke,dark:Pe},Ie="synapse-defaults",G="synapse-theme-defaults",W=new Set;function ve(){return typeof document<"u"&&typeof document.getElementById=="function"&&typeof document.createElement=="function"&&typeof document.head?.prepend=="function"}function we(e){if(!ve())return;let o=Object.entries(Ee[e]).map(([a,p])=>` ${a}: ${p};`).join(`
2
- `),n=`@layer ${Ie} {
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
- ${o}
4
+ ${n}
5
5
  }
6
- }`,s=document.getElementById(G);if(s){s.textContent!==n&&(s.textContent=n);return}let i=document.createElement("style");i.id=G,i.textContent=n,document.head.prepend(i);}function Ae(e,o){if(typeof document>"u")return;we(e);let n={};if(o&&typeof o=="object")for(let[i,a]of Object.entries(o))typeof i=="string"&&typeof a=="string"&&(n[i]=a);let s=document.documentElement.style;if(typeof s.removeProperty=="function")for(let i of W)i in n||s.removeProperty(i);for(let[i,a]of Object.entries(n))s.setProperty(i,a);W=new Set(Object.keys(n));}var F=new Set,Y="";function C(e){return Array.isArray(e)?JSON.stringify(e.map(o=>[o.family,o.src,o.weight,o.style,o.display])):""}function M(e){if(!Array.isArray(e))return;let o=e.filter(Oe);if(!(e.length>0&&o.length===0))return o}function Ce(e){let o=M(e);if(o===void 0||typeof document>"u"||!document.fonts||typeof FontFace>"u")return;let n=C(o);if(n!==Y){Y=n;for(let s of F)try{document.fonts.delete(s);}catch{}F.clear();for(let s of o)try{let i=new FontFace(s.family,s.src,{weight:s.weight,style:s.style,display:s.display&&be.has(s.display)?s.display:"swap"});document.fonts.add(i),F.add(i);}catch{}}}function Oe(e){if(!e||typeof e!="object")return false;let o=e;return typeof o.family=="string"&&typeof o.src=="string"}function V(e,o,n){Ae(e,o),Ce(n);}function R(e){typeof document<"u"&&document.documentElement.setAttribute("data-theme",e.mode),V(e.mode,e.tokens,e.fontFaces);}function O(e){try{if(e?.matchMedia?.("(prefers-color-scheme: dark)").matches)return "dark"}catch{}return "light"}function h(e,o){return e==="light"||e==="dark"?e:o}var T=class extends Error{constructor(o,n){super(`"${o}" is not supported by the "${n}" host`),this.name="HostUnsupportedError";}},_="synapse-ui-data",Z="ui-lifecycle-iframe-ready",$="ui-lifecycle-iframe-render-data",B="ui-size-change",q="link",Q="prompt",U="openai:set_globals",X="2026-01-26",J="ui/initialize",ee="ui/notifications/initialized",te="ui/notifications/tool-result",ne="ui/notifications/host-context-changed",oe="ui/notifications/size-changed",re="ui/open-link",se="ui/message",ie="ui/resource-teardown",ae="tools/call";function ce(e,o){let n=()=>e.openai,s=n()?.toolOutput??null,i={mode:h(n()?.theme,"light"),tokens:{}},a=new Set,p=new Set,f=false,m=c=>{if(f)return;let l=c.detail?.globals;if(l){if("toolOutput"in l&&l.toolOutput!=null){s=l.toolOutput;for(let u of a)u(s);}if("theme"in l&&l.theme!=null){let u=h(l.theme,i.mode);if(u!==i.mode){i={mode:u,tokens:{}};for(let b of p)b(i);}}}};return {host:"chatgpt",getData:()=>s,onData(c){return a.add(c),()=>a.delete(c)},getTheme:()=>i,onTheme(c){return p.add(c),()=>p.delete(c)},async callTool(c,l){let u=n()?.callTool;if(!u)throw new T("callTool","chatgpt");return await u(c,l??{})},sendPrompt(c){let l=n();(l?.sendFollowUpMessage??l?.sendFollowupMessage)?.({prompt:c});},openLink(c){let l=n()?.openExternal;l?l({href:c}):e.open(c,"_blank","noopener,noreferrer");},resize(){},capabilities(){let c=n();return {pull:typeof c?.callTool=="function",sendPrompt:typeof c?.sendFollowUpMessage=="function"||typeof c?.sendFollowupMessage=="function",openLink:true}},start(){e.addEventListener(U,m,{passive:true}),s=n()?.toolOutput??s;},destroy(){f||(f=true,e.removeEventListener(U,m),a.clear(),p.clear());}}}function x(e,o){if(!e)return null;let s=e.getElementById(o)?.textContent;if(!s)return null;try{return JSON.parse(s)??null}catch{return null}}function L(e){if(e==null||typeof e!="object")return null;let o=e,n=o.renderData,s=n!=null&&typeof n=="object"?n:o;return s.toolOutput!=null?s.toolOutput:s.structuredContent!=null?s.structuredContent:s}function le(e,o){let n=o.dataElementId??_,s=null,i={mode:O(e),tokens:{}},a=new Set,p=false,f=null,m=c=>{if(p)return;let l=h(c.matches?"dark":"light",i.mode);if(l!==i.mode){i={mode:l,tokens:{}};for(let u of a)u(i);}};return {host:"generic",getData:()=>s,onData(){return ()=>{}},getTheme:()=>i,onTheme(c){return a.add(c),()=>a.delete(c)},async callTool(c){throw new T("callTool","generic")},sendPrompt(){},openLink(c){e.open(c,"_blank","noopener,noreferrer");},resize(){},capabilities(){return {pull:false,sendPrompt:false,openLink:true}},start(){s=x(e.document,n);try{f=e.matchMedia?.("(prefers-color-scheme: dark)")??null,f?.addEventListener?.("change",m);}catch{f=null;}},destroy(){p||(p=true,f?.removeEventListener?.("change",m),f=null,a.clear());}}}var _e="synapse/fontFaces";function xe(e){return M(e?.[_e])}function de(e,o){return xe(o)??e}var De=3e4;function pe(e,o){let n=o.dataElementId??_,s=o.autoResize!==false,i=null,a={mode:O(e),tokens:{}},p=new Set,f=new Set,m=false,c=false,l=1,u=new Map,b=-1,v=null,k=null,H=()=>e.parent??e;function P(t){H().postMessage(t,"*");}function D(t){c||P(t);}function N(t,r){P({jsonrpc:"2.0",method:t,params:r??{}});}function w(t,r){let d=l++;return new Promise((g,y)=>{let I=setTimeout(()=>{u.delete(d),y(new Error(`"${t}" timed out`));},De);u.set(d,{resolve:g,reject:y,timer:I}),P({jsonrpc:"2.0",id:d,method:t,params:r??{}});})}function z(t){if(t!=null){i=t;for(let r of p)r(t);}}function S(t){if(!t||typeof t!="object")return;let{mode:r,tokens:d,fontFaces:g}=a,y=false;if(t.theme!=null){let A=h(t.theme,r);A!==r&&(r=A,y=true);}let I=t.styles;I?.variables&&typeof I.variables=="object"&&(d={...d,...I.variables},y=true);let K=de(g,t);if(C(K)!==C(g)&&(g=K,y=true),y){a={mode:r,tokens:d,...g?{fontFaces:g}:{}};for(let A of f)A(a);}}function E(t){if(m)return;let r=typeof t=="number"?t:Math.ceil(e.document.body.scrollHeight);r!==b&&(b=r,N(oe,{height:r}),D({type:B,payload:{height:r}}));}function me(t){let r=Number(t.id),d=u.get(r);if(d)if(u.delete(r),clearTimeout(d.timer),t.error!=null){let g=t.error;d.reject(new Error(g.message??"request failed"));}else d.resolve(t.result);}function ge(t,r){if(t===te){let d=r.structuredContent;z(d??L(r));}else t===ne&&S(r);}function ye(t){t.method===ie&&P({jsonrpc:"2.0",id:t.id,result:{}});}function he(t){if(t.type===$||t.type==="renderData"){let{theme:r,...d}=t.payload??{};r!=null&&S({theme:r}),Object.keys(d).length>0&&z(L(d));}}let j=t=>{if(m||t.source&&t.source!==H())return;let r=t.data;if(!(!r||typeof r!="object")){if(r.jsonrpc!=="2.0"){he(r);return}r.id!=null&&("result"in r||"error"in r)?me(r):typeof r.method=="string"&&(r.id!=null?ye(r):ge(r.method,r.params??{}));}};function Te(){k=()=>E(),e.addEventListener("resize",k),typeof e.ResizeObserver<"u"&&(v=new e.ResizeObserver(()=>E()),v.observe(e.document.body));}return {host:"claude",getData:()=>i,onData(t){return p.add(t),()=>p.delete(t)},getTheme:()=>a,onTheme(t){return f.add(t),()=>f.delete(t)},async callTool(t,r){return await w(ae,{name:t,arguments:r??{}})},sendPrompt(t){w(se,{role:"user",content:[{type:"text",text:t}]}).catch(()=>{}),D({type:Q,payload:{prompt:t}});},openLink(t){w(re,{url:t}).catch(()=>{}),D({type:q,payload:{url:t}});},resize(t){E(t);},capabilities(){return {pull:true,sendPrompt:true,openLink:true}},start(){e.addEventListener("message",j),i=x(e.document,n),s&&Te(),P({type:Z}),w(J,{appInfo:{name:o.name??"synapse-ui",version:o.version??"0.0.0"},appCapabilities:{availableDisplayModes:["inline"]},protocolVersion:X}).then(t=>{m||(c=true,S(t?.hostContext),N(ee,{}),b=-1,E());}).catch(()=>{}),E();},destroy(){if(!m){m=true,e.removeEventListener("message",j),k&&e.removeEventListener("resize",k),k=null,v?.disconnect(),v=null;for(let t of u.values())clearTimeout(t.timer),t.reject(new Error("adapter destroyed"));u.clear(),p.clear(),f.clear();}}}}function Se(e){if(e.openai!=null)return "chatgpt";try{if(e.parent!=null&&e.parent!==e)return "claude"}catch{return "claude"}return "generic"}function Fe(e,o,n){switch(e){case "chatgpt":return ce(o);case "claude":case "nimblebrain":return pe(o,n);default:return le(o,n)}}function ue(e,o){let n=o.host??Se(e);return Fe(n,e,o)}function fe(e={}){let o=e.window??globalThis,n=ue(o,e);R(n.getTheme());let s=n.onTheme(R);n.start();let i=false;return {data:()=>n.getData(),onData:a=>n.onData(a),theme:()=>n.getTheme(),onTheme:a=>n.onTheme(a),callTool:(a,p)=>n.callTool(a,p),sendPrompt:a=>n.sendPrompt(a),openLink:a=>n.openLink(a),resize:a=>n.resize(a),capabilities:()=>n.capabilities(),host:()=>n.host,destroy(){i||(i=true,s(),n.destroy());}}}globalThis.SynapseUI={connect:fe};})();
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 (and mirrored back by the host in `hostCapabilities.tasks`).
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
- * Only a NimbleBrain host consumes `synapse/keydown`, so forwarding stays
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" | "data-changed" | "action" | "teardown";
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`, `downloadFile`) and the MCP tasks utility are composable functions
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
- /** True when the host identified itself as NimbleBrain in the handshake. */
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
- callTool<TOutput = unknown>(name: string, args?: Record<string, unknown>, options?: CallToolOptions): Promise<ToolCallResult<TOutput>>;
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
- /** Send a user message into the agent conversation (ext-apps `ui/message`). */
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, BuiltinActionType as B, ConnectOptions as C, DataChangedEvent as D, FileResult as F, HostInfo as H, KeyForwardConfig as K, ModelContext as M, NavigatePayload as N, RequestFileOptions as R, TaskHandle as T, CallToolAsTaskOptions as a, ToolCallResult as b, CallToolOptions as c, Theme as d, ToolResultData as e, ToolDefinition as f, FontFaceDescriptor as g, AgentAction as h, AppEventName as i, Dimensions as j, FontDisplayValue as k, NotifyPayload as l, TasksCapability as m };
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 (and mirrored back by the host in `hostCapabilities.tasks`).
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
- * Only a NimbleBrain host consumes `synapse/keydown`, so forwarding stays
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" | "data-changed" | "action" | "teardown";
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`, `downloadFile`) and the MCP tasks utility are composable functions
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
- /** True when the host identified itself as NimbleBrain in the handshake. */
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
- callTool<TOutput = unknown>(name: string, args?: Record<string, unknown>, options?: CallToolOptions): Promise<ToolCallResult<TOutput>>;
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
- /** Send a user message into the agent conversation (ext-apps `ui/message`). */
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, BuiltinActionType as B, ConnectOptions as C, DataChangedEvent as D, FileResult as F, HostInfo as H, KeyForwardConfig as K, ModelContext as M, NavigatePayload as N, RequestFileOptions as R, TaskHandle as T, CallToolAsTaskOptions as a, ToolCallResult as b, CallToolOptions as c, Theme as d, ToolResultData as e, ToolDefinition as f, FontFaceDescriptor as g, AgentAction as h, AppEventName as i, Dimensions as j, FontDisplayValue as k, NotifyPayload as l, TasksCapability as m };
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 };