@databrainhq/mcp-server 0.3.0-rc.2 → 0.3.0-rc.3
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/apps/server.js +2 -2
- package/dist/apps/stdio.js +3 -3
- package/dist/{chunk-SG7F7PRY.js → chunk-4LIULTLT.js} +103 -38
- package/dist/chunk-4LIULTLT.js.map +1 -0
- package/dist/{chunk-3AFT7S6E.js → chunk-DZOYK5BB.js} +13 -4
- package/dist/chunk-DZOYK5BB.js.map +1 -0
- package/dist/{chunk-4OFVDEFU.js → chunk-OHTV24NG.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{main-RYW6PTR4.js → main-VPCN2764.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-3AFT7S6E.js.map +0 -1
- package/dist/chunk-SG7F7PRY.js.map +0 -1
- /package/dist/{chunk-4OFVDEFU.js.map → chunk-OHTV24NG.js.map} +0 -0
- /package/dist/{main-RYW6PTR4.js.map → main-VPCN2764.js.map} +0 -0
package/dist/apps/server.js
CHANGED
package/dist/apps/stdio.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadConfig
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-OHTV24NG.js";
|
|
5
5
|
import {
|
|
6
6
|
createChartAppServer
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-4LIULTLT.js";
|
|
8
|
+
import "../chunk-DZOYK5BB.js";
|
|
9
9
|
|
|
10
10
|
// src/apps/stdio.ts
|
|
11
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
KPI_URI,
|
|
7
7
|
MCP_SERVER_VERSION,
|
|
8
8
|
READ_ONLY,
|
|
9
|
+
appMeta,
|
|
9
10
|
chartResult,
|
|
10
11
|
createServiceBundle,
|
|
11
12
|
enableChartApps,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
registerAllTools,
|
|
17
18
|
toolContext,
|
|
18
19
|
viewSchema
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-DZOYK5BB.js";
|
|
20
21
|
|
|
21
22
|
// src/apps/server.ts
|
|
22
23
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -86,16 +87,34 @@ function createChartBackend(client) {
|
|
|
86
87
|
// src/apps/tile.ts
|
|
87
88
|
var TILE_SCRIPT = String.raw`(()=>{
|
|
88
89
|
const $=id=>document.getElementById(id),root=document.documentElement,pending=new Map();
|
|
89
|
-
let seq=0,origin='*',ready=false,closed=false,ref=null,view={},next=null,context={},busy=false,revision=0,rawSvg=null;
|
|
90
|
+
let seq=0,origin='*',ready=false,closed=false,ref=null,view={},next=null,context={},busy=false,revision=0,rawSvg=null,dialect=null,shown=false;
|
|
91
|
+
/* One lifecycle, two sets of names. Hosts ship either or both, so notifications go out
|
|
92
|
+
under both and arrive under either. Same order in both rows: result, input, context. */
|
|
93
|
+
const DASH=['ui/notifications/tool-result','ui/notifications/tool-input','ui/notifications/host-context-changed'],
|
|
94
|
+
SNAKE=['notifications/tools/call/result','notifications/tools/call/input','notifications/host_context/changed'],
|
|
95
|
+
INIT=['ui/notifications/initialized','notifications/initialized'],
|
|
96
|
+
SIZE=['ui/notifications/size-changed','notifications/size_changed'],
|
|
97
|
+
DISPLAY=['ui/request-display-mode','requestDisplayMode'];
|
|
90
98
|
function send(message){if(!closed)parent.postMessage({jsonrpc:'2.0',...message},origin)}
|
|
99
|
+
function notify(names,params){for(const method of names)send({method,params})}
|
|
91
100
|
function rpc(method,params){return new Promise((resolve,reject)=>{const id=++seq;
|
|
92
101
|
const timer=setTimeout(()=>{pending.delete(id);reject(Error('timeout'))},30000);
|
|
93
102
|
pending.set(id,{resolve,reject,timer});send({id,method,params})})}
|
|
103
|
+
/* Try each alias while the host says it never heard of it; any other error stands. */
|
|
104
|
+
async function ask(names,params){let last;
|
|
105
|
+
for(const method of names){try{return await rpc(method,params)}catch(error){last=error;if(error.code!==-32601)throw error}}
|
|
106
|
+
throw last}
|
|
107
|
+
/* Fixed code only. The tile sits inside customer rows; a log is no place to leak them. */
|
|
108
|
+
function trouble(code){send({method:'notifications/error',params:{code}})}
|
|
94
109
|
function status(text,bad){const s=$('status');s.textContent=text;s.toggleAttribute('data-error',!!bad);if(bad)$('plot').dataset.state='off'}
|
|
95
|
-
function paint(){const img=$('chart-image');if(img
|
|
110
|
+
function paint(){const img=$('chart-image');if(!img||!rawSvg)return;
|
|
111
|
+
const c=getComputedStyle(document.body).color,mix=pc=>'color-mix(in srgb,'+c+' '+pc+'%,transparent)';
|
|
112
|
+
img.src='data:image/svg+xml,'+encodeURIComponent(rawSvg.replace(/<\/svg>\s*$/i,'<style>text{fill:'+c+'!important}'
|
|
113
|
+
+'[stroke="#dbdee4"]{stroke:'+mix(13)+'!important}[stroke="#54555a"]{stroke:'+mix(30)+'!important}'
|
|
114
|
+
+'</style></svg>'))}
|
|
96
115
|
function controls(){document.querySelectorAll('button,input,select').forEach(e=>{e.disabled=busy});
|
|
97
116
|
$('refresh').hidden=!ref;$('next').hidden=!next;$('range').hidden=!ref;$('plot').toggleAttribute('data-busy',busy);
|
|
98
|
-
$('expand').hidden=!context.availableDisplayModes?.includes('fullscreen');
|
|
117
|
+
$('expand').hidden=!shown||!context.availableDisplayModes?.includes('fullscreen');
|
|
99
118
|
root.toggleAttribute('data-deep',context.displayMode==='fullscreen'||$('expand').hidden)}
|
|
100
119
|
function host(change){context={...context,...change};if(change.theme==='dark'||change.theme==='light')root.style.colorScheme=change.theme;
|
|
101
120
|
const vars=change.styles?.variables||{};for(const [key,value] of Object.entries(vars)){
|
|
@@ -105,61 +124,82 @@ const insets=change.safeAreaInsets;if(insets)for(const edge of ['top','right','b
|
|
|
105
124
|
const n=insets[edge];if(Number.isFinite(n))root.style.setProperty('--safe-'+edge,Math.max(0,Math.min(n,200))+'px')}
|
|
106
125
|
root.dataset.mode=context.displayMode==='fullscreen'?'full':'inline';
|
|
107
126
|
$('expand').textContent=context.displayMode==='fullscreen'?'Back to conversation':'Expand';paint();controls()}
|
|
108
|
-
function
|
|
127
|
+
function scope(p,n){const bits=[];
|
|
128
|
+
if(p.kind==='chart'||p.kind==='table')bits.push(n?(n===1?'1 row':n+' rows'):'No rows in this view');
|
|
129
|
+
if(view.dateRange)bits.push(view.dateRange.from+' to '+view.dateRange.to);
|
|
130
|
+
if(next)bits.push('more pages available');
|
|
131
|
+
$('scope').textContent=bits.join(' · ')}
|
|
132
|
+
function resize(){if(ready)notify(SIZE,{width:document.body.scrollWidth,height:document.body.scrollHeight})}
|
|
109
133
|
function table(rows,columns){$('rows').replaceChildren();$('data').hidden=!rows?.length;if(!rows?.length)return;
|
|
110
|
-
$('count').textContent=rows.length===1?'
|
|
134
|
+
$('count').textContent=rows.length===1?'1 row behind this':rows.length+' rows behind this';
|
|
111
135
|
const t=document.createElement('table');
|
|
112
|
-
const
|
|
136
|
+
const num=name=>rows.some(r=>typeof r[name]==='number');
|
|
137
|
+
const header=document.createElement('tr');for(const name of columns){const th=document.createElement('th');th.scope='col';th.textContent=name;if(num(name))th.className='n';header.append(th)}
|
|
113
138
|
const head=document.createElement('thead');head.append(header);t.append(head);const body=document.createElement('tbody');
|
|
114
|
-
for(const row of rows.slice(0,50)){const tr=document.createElement('tr');for(const key of columns){const td=document.createElement('td');td.textContent=String(row[key]??'');tr.append(td)}body.append(tr)}t.append(body);$('rows').append(t)}
|
|
115
|
-
async function display(mode){try{const answer=await
|
|
139
|
+
for(const row of rows.slice(0,50)){const tr=document.createElement('tr');for(const key of columns){const td=document.createElement('td');td.textContent=String(row[key]??'');if(num(key))td.className='n';tr.append(td)}body.append(tr)}t.append(body);$('rows').append(t)}
|
|
140
|
+
async function display(mode){try{const answer=await ask(DISPLAY,{mode});
|
|
116
141
|
host({displayMode:answer.mode||context.displayMode});if(root.dataset.mode==='full'&&!$('data').hidden)$('data').open=true;resize()}
|
|
117
142
|
catch{status('Expanded view is unavailable in this host.',1)}}
|
|
118
143
|
function wait(){busy=true;if(!$('plot').firstChild)$('plot').dataset.state='wait';status('Loading chart…');controls()}
|
|
119
144
|
async function call(name,extra={}){if(!ready||!ref||busy)return;wait();const rev=++revision;
|
|
120
145
|
try{const result=await rpc('tools/call',{name,arguments:{reference:ref,view:{...view,...extra},context:'User is interacting with the chart tile.'}});
|
|
121
|
-
if(rev===revision)render(result)}catch{if(rev===revision)status('Could not load the chart. Select Refresh to try again.',1)}finally{busy=false;controls()}}
|
|
146
|
+
if(rev===revision)render(result)}catch{if(rev===revision){trouble('tool-call-failed');status('Could not load the chart. Select Refresh to try again.',1)}}finally{busy=false;controls()}}
|
|
122
147
|
function render(result){busy=false;controls();
|
|
123
|
-
if(result?.isError){
|
|
124
|
-
|
|
125
|
-
|
|
148
|
+
if(result?.isError){trouble('tool-returned-error');shown=false;controls();
|
|
149
|
+
status(ref?'Chart unavailable. Check your access to this dashboard, then select Refresh.'
|
|
150
|
+
:'Chart unavailable. Ask for this chart again to retry.',1);return}
|
|
151
|
+
const p=result?.structuredContent;if(!p||typeof p!=='object'){shown=false;controls();status('No chart data came back for this result.',1);return}
|
|
152
|
+
revision++;shown=true;rawSvg=null;$('plot').replaceChildren();$('plot').dataset.state='ready';$('choices').replaceChildren();delete $('rail').dataset.value;
|
|
126
153
|
ref=p.reference||null;view=p.view||{};next=p.nextCursor||null;
|
|
127
154
|
$('title').textContent=p.title||'Analytics';$('description').textContent=p.description||'';
|
|
128
155
|
if(p.kind==='dashboard'){$('plot').dataset.state='off';
|
|
129
156
|
for(const item of p.widgets||[]){const b=document.createElement('button');b.textContent=item.name||'Untitled widget';b.onclick=()=>{ref=item.reference;view={};call('render_chart')};$('choices').append(b)}
|
|
130
|
-
status(p.widgets?.length?'
|
|
157
|
+
status(p.widgets?.length?'':'This dashboard has no charts yet. Add one in Databrain, then ask again.');
|
|
131
158
|
}else if(p.kind==='kpi'){
|
|
132
159
|
const number=document.createElement('p');number.className='kpi';number.textContent=String(p.value??'—');$('plot').append(number);status('');
|
|
133
160
|
}else if(p.svg){
|
|
134
161
|
const box=document.createElement('div');box.className='chart';box.style.aspectRatio=p.width+'/'+p.height;
|
|
135
|
-
const img=document.createElement('img');img.id='chart-image';rawSvg=p.svg;img.alt=p.title||'Chart';img.onerror=()=>{status('
|
|
162
|
+
const img=document.createElement('img');img.id='chart-image';rawSvg=p.svg;img.alt=p.title||'Chart';img.onerror=()=>{status('The chart image could not be drawn. The rows behind it are below.',1);$('data').open=$('data').toggleAttribute('data-only',true)};box.append(img);
|
|
136
163
|
for(const point of p.points||[]){const b=document.createElement('button');b.className='point';b.setAttribute('aria-label',point.label+': '+point.value);b.setAttribute('aria-pressed','false');
|
|
137
164
|
b.style.left=100*point.x/p.width+'%';b.style.top=100*point.y/p.height+'%';b.style.width=100*point.width/p.width+'%';b.style.height=100*point.height/p.height+'%';
|
|
138
|
-
const hover=()=>{$('
|
|
165
|
+
const hover=()=>{$('vlabel').textContent=point.label;$('vnum').textContent=point.value;$('rail').dataset.value='1'};b.onmouseenter=hover;b.onfocus=hover;
|
|
139
166
|
b.onclick=()=>{box.querySelectorAll('.point').forEach(e=>e.setAttribute('aria-pressed','false'));b.setAttribute('aria-pressed','true');hover();
|
|
140
167
|
$('choices').replaceChildren();if(point.drillId){const drill=document.createElement('button');drill.className='primary';drill.textContent='Explore selected data';drill.onclick=()=>call('drill_down',{drillId:point.drillId,cursor:undefined});$('choices').append(drill)}};box.append(b)}$('plot').append(box);
|
|
141
|
-
status(
|
|
142
|
-
}else
|
|
143
|
-
|
|
144
|
-
|
|
168
|
+
status('');
|
|
169
|
+
}else if(p.kind==='table'){
|
|
170
|
+
// ask_question returns a table by design; calling that a missing chart called success a failure.
|
|
171
|
+
$('plot').dataset.state='off';status('');
|
|
172
|
+
}else{$('plot').dataset.state='off';
|
|
173
|
+
status('Nothing to show for this view. Try a wider date range, then select Refresh.',1)}
|
|
174
|
+
paint();table(p.rows||[],p.columns||Object.keys(p.rows?.[0]||{}));scope(p,p.rows?.length||0);
|
|
175
|
+
const only=!p.svg&&p.value===undefined&&p.kind!=='dashboard';$('data').open=only;$('data').toggleAttribute('data-only',only);controls();resize()}
|
|
145
176
|
$('refresh').onclick=()=>call('refresh_widget',{cursor:undefined});$('next').onclick=()=>call('render_chart',{cursor:next});
|
|
146
177
|
$('expand').onclick=()=>display(context.displayMode==='fullscreen'?'inline':'fullscreen');
|
|
147
178
|
$('dates').onclick=()=>{const from=$('from').value,to=$('to').value;if(!from||!to||from>to){status('Choose a start date on or before the end date.',1);return}call('change_date_range',{dateRange:{from,to},cursor:undefined,drillId:undefined})};
|
|
148
179
|
function receive(event){if(event.source!==parent||closed)return;const m=event.data;
|
|
149
180
|
if(!m||Array.isArray(m)||m.jsonrpc!=='2.0'||(origin!=='*'&&event.origin!==origin))return;
|
|
150
181
|
if(!m.method&&pending.has(m.id)&&('result'in m||'error'in m)){if(origin==='*'&&event.origin!=='null')origin=event.origin;
|
|
151
|
-
const p=pending.get(m.id);pending.delete(m.id);clearTimeout(p.timer);
|
|
182
|
+
const p=pending.get(m.id);pending.delete(m.id);clearTimeout(p.timer);
|
|
183
|
+
if(m.error){const error=Error('host error');error.code=m.error.code;p.reject(error)}else p.resolve(m.result);return}
|
|
152
184
|
if(m.method==='ping'&&m.id!==undefined){send({id:m.id,result:{}});return}
|
|
153
185
|
if(m.method==='ui/resource-teardown'){send({id:m.id,result:{}});closed=true;revision++;for(const p of pending.values()){clearTimeout(p.timer);p.reject(Error('closed'))}pending.clear();observer.disconnect();removeEventListener('message',receive);return}
|
|
154
186
|
if(!ready||m.id!==undefined)return;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
187
|
+
let slot=DASH.indexOf(m.method),kind='dashed';
|
|
188
|
+
if(slot<0){slot=SNAKE.indexOf(m.method);kind='snake'}
|
|
189
|
+
if(slot>=0){
|
|
190
|
+
/* A dual host posts each notification twice. Lock to the first name and drop its twin:
|
|
191
|
+
same payload, and a second render is a visible flicker. */
|
|
192
|
+
if(!dialect)dialect=kind;if(dialect!==kind)return;
|
|
193
|
+
// The snake result nests one level deeper.
|
|
194
|
+
if(slot===0)render(kind==='snake'?m.params&&m.params.result:m.params);
|
|
195
|
+
else if(slot===1)wait();
|
|
196
|
+
else{host(m.params||{});resize()}
|
|
197
|
+
return}
|
|
198
|
+
if(m.method==='ui/notifications/tool-cancelled'){revision++;busy=false;$('plot').dataset.state='off';status('Chart request cancelled.');controls()}}
|
|
159
199
|
addEventListener('message',receive);const observer=new ResizeObserver(resize);observer.observe(document.body);controls();
|
|
160
200
|
rpc('ui/initialize',{protocolVersion:'2026-01-26',appInfo:{name:'Databrain chart tile',version:'1.0.0'},appCapabilities:{availableDisplayModes:['inline','fullscreen']}})
|
|
161
|
-
.then(r=>{if(r.protocolVersion!=='2026-01-26')throw Error('version');host(r.hostContext||{});ready=true;
|
|
162
|
-
.catch(()=>status('This chart needs a host that supports MCP Apps.',1));
|
|
201
|
+
.then(r=>{if(r.protocolVersion!=='2026-01-26')throw Error('version');host(r.hostContext||{});ready=true;notify(INIT,{});resize()})
|
|
202
|
+
.catch(()=>{trouble('initialize-failed');status('This chart needs a host that supports MCP Apps.',1)});
|
|
163
203
|
})();`;
|
|
164
204
|
function tileHtml(kind = "chart") {
|
|
165
205
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
@@ -170,7 +210,8 @@ function tileHtml(kind = "chart") {
|
|
|
170
210
|
:root{color-scheme:light dark;
|
|
171
211
|
--dbn-bg:var(--color-background-primary,Canvas);--dbn-text:var(--color-text-primary,CanvasText);
|
|
172
212
|
--dbn-mute:var(--color-text-secondary,GrayText);--dbn-faint:var(--color-text-tertiary,GrayText);
|
|
173
|
-
--dbn-edge:var(--color-border-secondary,
|
|
213
|
+
--dbn-edge:var(--color-border-secondary,color-mix(in srgb,currentColor 20%,transparent));
|
|
214
|
+
--dbn-line:var(--color-border-tertiary,color-mix(in srgb,currentColor 9%,transparent));
|
|
174
215
|
--dbn-accent:var(--color-text-info,LinkText);--dbn-ring:var(--color-ring-primary,var(--dbn-accent));
|
|
175
216
|
--dbn-bad:var(--color-text-danger,var(--dbn-text));--dbn-bad-edge:var(--color-border-danger,currentColor);
|
|
176
217
|
--dbn-quiet:color-mix(in srgb,currentColor 6%,transparent);
|
|
@@ -189,9 +230,21 @@ button,input{font:inherit;color:inherit;background:var(--dbn-bg);border:var(--bw
|
|
|
189
230
|
button{cursor:pointer;transition:background-color var(--q) var(--e),border-color var(--q) var(--e)}
|
|
190
231
|
input{max-width:160px;min-width:0}button:hover:not(:disabled){background:var(--dbn-quiet)}button:disabled{color:var(--dbn-faint);cursor:default}
|
|
191
232
|
button:focus-visible,input:focus-visible,summary:focus-visible{outline:2px solid var(--dbn-ring);outline-offset:2px}
|
|
192
|
-
#choices button
|
|
233
|
+
#choices button{text-align:left;border-color:transparent;border-bottom-color:var(--dbn-line);border-radius:0;padding:10px 2px}
|
|
234
|
+
#choices button:first-child{border-top:var(--bw) solid var(--dbn-line)}
|
|
235
|
+
#choices button:hover:not(:disabled){background:var(--dbn-quiet);border-radius:var(--r-s)}
|
|
236
|
+
/* One primary per state: the drill pill, and Next page when there is a next page. */
|
|
237
|
+
#choices button.primary,#next{flex:none;text-align:center;border:var(--bw) solid var(--dbn-accent);
|
|
238
|
+
border-radius:var(--r-s);padding:7px 10px;font-weight:var(--med);color:var(--dbn-accent)}
|
|
239
|
+
#choices button.primary{align-self:flex-start;margin-top:10px}
|
|
240
|
+
/* Refresh is a re-fetch, not a choice. Quiet it so it stops reading as a fifth chart. */
|
|
241
|
+
#refresh{border-color:transparent;color:var(--dbn-mute);padding-left:2px;padding-right:8px}
|
|
242
|
+
#refresh:hover:not(:disabled){color:var(--dbn-text);background:none;text-decoration:underline}
|
|
193
243
|
#status:empty{margin:0}
|
|
194
|
-
#status[data-error]{color:var(--dbn-bad);font-weight:var(--med);
|
|
244
|
+
#status[data-error]{color:var(--dbn-bad);font-weight:var(--med);
|
|
245
|
+
border-left:2px solid var(--color-border-danger,color-mix(in srgb,currentColor 40%,transparent));
|
|
246
|
+
background:var(--color-background-danger,color-mix(in srgb,currentColor 5%,transparent));
|
|
247
|
+
border-radius:0 var(--r-m) var(--r-m) 0;padding:10px 12px;margin:10px 0}
|
|
195
248
|
/* Reserved plot. The skeleton is a static bar silhouette matching the 15:8 backend render:
|
|
196
249
|
Claude's guidance asks for a skeleton that matches the final layout, and bans nothing here,
|
|
197
250
|
but shimmer is banned by our own motion budget - so this paints once and never repaints. */
|
|
@@ -211,9 +264,18 @@ outline:2px solid transparent;outline-offset:1px;transition:background-color var
|
|
|
211
264
|
.point:hover{background:color-mix(in srgb,currentColor 12%,transparent)}
|
|
212
265
|
.point[aria-pressed=true]{outline-color:var(--dbn-accent);background:color-mix(in srgb,var(--dbn-accent) 18%,transparent)}
|
|
213
266
|
.kpi{font-size:clamp(34px,10vw,44px);line-height:1.1;font-weight:var(--semi);letter-spacing:-.025em;font-variant-numeric:tabular-nums;color:var(--dbn-text);margin:14px 0 6px;transition:font-size var(--l) var(--e)}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
#
|
|
267
|
+
/* The readout rail. At rest it states the scope of what is on screen; on selection it
|
|
268
|
+
becomes the value. One slot, two jobs, so selecting a bar never reflows the tile. */
|
|
269
|
+
#rail{display:flex;align-items:center;min-height:2.5em;margin:2px 0 0}
|
|
270
|
+
#scope,#value{margin:0;display:none;overflow-wrap:anywhere}
|
|
271
|
+
#rail:not([data-value]) #scope{display:block;color:var(--dbn-mute);font-size:var(--font-text-xs-size,12px)}
|
|
272
|
+
#rail:not([data-value]):has(#scope:empty){display:none}
|
|
273
|
+
#rail[data-value] #value{display:flex;flex-direction:column;gap:1px;line-height:1.15}
|
|
274
|
+
#vlabel{color:var(--dbn-mute);font-size:var(--font-text-xs-size,12px)}
|
|
275
|
+
#vnum{font-size:var(--font-heading-md-size,18px);font-weight:var(--semi);letter-spacing:-.015em;font-variant-numeric:tabular-nums}
|
|
276
|
+
[data-mode=full] #vnum{font-size:var(--font-heading-xl-size,24px)}
|
|
277
|
+
footer,.dates{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
|
278
|
+
#choices{display:flex;flex-direction:column;align-items:stretch;margin:10px 0 0}footer{margin-top:12px}.dates{margin:8px 0 2px}
|
|
217
279
|
label{display:flex;align-items:center;gap:6px;color:var(--dbn-mute)}
|
|
218
280
|
#range{margin-top:10px}#data{margin-top:2px}#data[hidden],#range[hidden]{display:none}
|
|
219
281
|
html:not([data-deep]) #range,html:not([data-deep]) #data:not([data-only]){display:none}
|
|
@@ -221,7 +283,9 @@ summary{cursor:pointer;color:var(--dbn-mute);font-size:var(--font-text-xs-size,1
|
|
|
221
283
|
#rows{overflow:auto;max-height:260px;border-radius:var(--r-s)}
|
|
222
284
|
table{border-collapse:collapse;width:100%;font-size:var(--font-text-xs-size,12px)}
|
|
223
285
|
th,td{text-align:left;padding:8px;border-bottom:var(--bw) solid var(--dbn-line);overflow-wrap:anywhere}
|
|
224
|
-
td:
|
|
286
|
+
td.n,th.n{text-align:right;font-variant-numeric:tabular-nums}
|
|
287
|
+
th{font-weight:var(--semi);position:sticky;top:0;background:var(--dbn-bg);color:var(--dbn-mute);
|
|
288
|
+
font-size:var(--font-text-xs-size,12px);letter-spacing:.02em;text-transform:uppercase}
|
|
225
289
|
/* Inline to fullscreen is a layout morph, not a new screen: main widens over 400ms on the
|
|
226
290
|
one curve and every dependent box follows it. Fullscreen is the detailed view, so the
|
|
227
291
|
table loses its inline height cap. */
|
|
@@ -239,11 +303,12 @@ td:last-child{font-variant-numeric:tabular-nums}th{font-weight:var(--semi);posit
|
|
|
239
303
|
@keyframes rise{from{opacity:0;transform:translateY(6px)}}
|
|
240
304
|
@keyframes land{from{opacity:0;transform:translateY(4px) scale(.995)}}
|
|
241
305
|
header,#status,#plot{animation:rise var(--s) var(--e) both}
|
|
242
|
-
#
|
|
306
|
+
#rail,#choices,footer,#range,#data{animation:rise var(--s) var(--e) 60ms both}
|
|
243
307
|
.chart,.kpi{animation:land var(--s) var(--e) both}
|
|
244
308
|
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}
|
|
245
309
|
</style></head><body><main id="tile"><header><div><h1 id="title">Analytics</h1><p id="description"></p></div><button id="expand" hidden>Expand</button></header>
|
|
246
|
-
<p id="status" role="status">Loading chart\u2026</p><div id="plot" data-state="wait"></div
|
|
310
|
+
<p id="status" role="status">Loading chart\u2026</p><div id="plot" data-state="wait"></div>
|
|
311
|
+
<div id="rail"><p id="scope"></p><p id="value"><span id="vlabel"></span><b id="vnum"></b></p></div><div id="choices"></div>
|
|
247
312
|
<footer><button id="refresh" hidden>Refresh</button><button id="next" hidden>Next page</button></footer>
|
|
248
313
|
<details id="range" hidden><summary>Change the date range</summary><div class="dates"><label>From <input id="from" type="date"></label><label>To <input id="to" type="date"></label><button id="dates" type="button">Apply dates</button></div></details>
|
|
249
314
|
<details id="data" hidden><summary id="count">Show the data behind this</summary><div id="rows" tabindex="0" role="region" aria-label="Chart data"></div></details></main><script>${TILE_SCRIPT}</script></body></html>`;
|
|
@@ -285,7 +350,7 @@ function registerChartApps(server, client, backend = createChartBackend(client))
|
|
|
285
350
|
}] };
|
|
286
351
|
}
|
|
287
352
|
});
|
|
288
|
-
tool.update({ _meta:
|
|
353
|
+
tool.update({ _meta: appMeta(["app"]) });
|
|
289
354
|
}
|
|
290
355
|
for (const [uri, kind] of [[CHART_URI, "chart"], [KPI_URI, "kpi"]]) {
|
|
291
356
|
server.resource(`Databrain ${kind} tile`, uri, { mimeType: APP_MIME, description: `Interactive ${kind} using host theme and scoped server tools` }, async () => ({
|
|
@@ -311,4 +376,4 @@ function createChartAppServer(config, backend) {
|
|
|
311
376
|
export {
|
|
312
377
|
createChartAppServer
|
|
313
378
|
};
|
|
314
|
-
//# sourceMappingURL=chunk-
|
|
379
|
+
//# sourceMappingURL=chunk-4LIULTLT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/server.ts","../src/apps/registry.ts","../src/apps/backend.ts","../src/apps/tile.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { DatabrainClient } from '../client/databrain-client.js';\nimport type { Config } from '../utils/config.js';\nimport { registerAllTools } from '../tools/registry.js';\nimport { registerAllResources } from '../resources/registry.js';\nimport { registerAllPrompts } from '../prompts/registry.js';\nimport { MCP_SERVER_VERSION } from '../version.js';\nimport { enableChartApps } from './feature.js';\nimport { registerChartApps } from './registry.js';\nimport type { ChartBackend } from './backend.js';\n\n/** Reviewable composition seam; hosted owners must retain their principal-bound client/policy. */\nexport function createChartAppServer(config: Config, backend?: ChartBackend): McpServer {\n const client = new DatabrainClient(config);\n const server = new McpServer({ name: 'databrain-chart-apps', version: MCP_SERVER_VERSION }, {\n instructions: 'Use read-only chart previews to inspect analytics. Chart interactions inherit guest scope. ' +\n 'Never request or expose credentials. Mutation tools retain their existing dry-run and confirmation requirements.',\n });\n enableChartApps(server);\n registerAllTools(server, client);\n registerAllResources(server);\n registerAllPrompts(server);\n registerChartApps(server, client, backend);\n return server;\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { DatabrainClient } from '../client/databrain-client.js';\nimport { READ_ONLY } from '../tools/annotations.js';\nimport { toolContext } from '../utils/tool-context.js';\nimport { createChartBackend, type ChartBackend, type ChartAction } from './backend.js';\nimport { APP_MIME, CHART_URI, KPI_URI, pageSchema, referenceSchema, viewSchema } from './contract.js';\nimport { appMeta, chartResult } from './feature.js';\nimport { tileHtml } from './tile.js';\n\nconst actions: Record<ChartAction, string> = {\n render_chart: 'Render one authorized chart page as SVG with point metadata. Cursor pagination stays within the same guest scope.',\n refresh_widget: 'Refresh a chart in the current guest scope. Revalidates access before returning data.',\n drill_down: 'Explore a server-issued drill identifier within the current chart and guest scope.',\n change_date_range: 'Render a chart for an ordered date range within the current guest scope.',\n};\n\n/** Call after registerAllTools so existing registration policy/instrumentation remains active. */\nexport function registerChartApps(server: McpServer, client: DatabrainClient, backend: ChartBackend = createChartBackend(client)): void {\n for (const [name, description] of Object.entries(actions)) {\n const action = name as ChartAction;\n const tool = server.tool(name, description, {\n reference: referenceSchema, view: viewSchema.default({}),\n context: z.string().max(200).optional().describe('Chart interaction context'),\n }, READ_ONLY, async ({ reference, view }) => {\n try {\n if ((action === 'drill_down' && !view.drillId) || (action === 'change_date_range' && !view.dateRange)) throw new Error('Missing view selector');\n const page = pageSchema.parse(await toolContext.run({ tool: name, category: 'embedding', workflowIntent: 'chart_app', inputMode: 'app' },\n () => backend.execute(action, reference, view)));\n // SVG is rendered as an isolated image, never inserted into the HTML DOM.\n // Reject active/external/animated constructs as a further contract check.\n if (page.svg && (!/^\\s*<svg\\b/i.test(page.svg) ||\n /<\\s*(?:script|foreignObject|iframe|image|animate\\w*|set)\\b|<!DOCTYPE|<!ENTITY|\\bon\\w+\\s*=|@(?:import|keyframes)|\\banimation\\s*:|(?:href\\s*=\\s*[\"'](?!#)|url\\s*\\(\\s*[\"']?(?!#))/i.test(page.svg))) {\n throw new Error('Unsafe SVG');\n }\n return chartResult(`${page.kind === 'kpi' ? 'KPI' : 'Chart'} ready. ${page.rows.length} rows in this page.`, {\n ...page, reference, view,\n });\n } catch {\n return { isError: true, content: [{ type: 'text' as const,\n text: 'Chart unavailable. Check dashboard access and the chart renderer integration, then retry.' }] };\n }\n });\n tool.update({ _meta: appMeta(['app']) });\n }\n for (const [uri, kind] of [[CHART_URI, 'chart'], [KPI_URI, 'kpi']] as const) {\n server.resource(`Databrain ${kind} tile`, uri, { mimeType: APP_MIME, description: `Interactive ${kind} using host theme and scoped server tools` }, async () => ({\n contents: [{ uri, mimeType: APP_MIME, text: tileHtml(kind), _meta: { ui: { prefersBorder: true } } }],\n }));\n }\n}\n","import { DatabrainClient } from '../client/databrain-client.js';\nimport type { RequestOptions } from '../client/types.js';\nimport { createServiceBundle } from '../services/factory.js';\nimport { pageSchema, referenceSchema, viewSchema, type ChartPage, type ChartReference, type ChartView } from './contract.js';\n\nexport const RENDER_PATH = '/api/v2/dashboard/mcp-chart';\nexport type ChartAction = 'render_chart' | 'refresh_widget' | 'drill_down' | 'change_date_range';\nexport interface ChartBackend {\n execute(action: ChartAction, reference: ChartReference, view: ChartView): Promise<ChartPage>;\n}\n\nclass UncachedChartClient extends DatabrainClient {\n constructor(private readonly delegate: DatabrainClient) {\n super({ apiUrl: delegate.getApiUrl(), demoDomain: delegate.getDemoDomain() });\n }\n override request<T>(options: RequestOptions): Promise<T> {\n return this.delegate.request<T>({ ...options, cacheKey: undefined, cacheTtlMs: undefined });\n }\n}\n\n/** Proposed backend route; no local renderer, ambient service fallback, or UI credentials. */\nexport function createChartBackend(client: DatabrainClient): ChartBackend {\n // The general embed lookup caches for five minutes. Authorization must not.\n const services = createServiceBundle(new UncachedChartClient(client));\n return {\n async execute(action, rawReference, rawView) {\n const reference = referenceSchema.parse(rawReference);\n const view = viewSchema.parse(rawView);\n // Every page/refresh/drill re-reads the embed live, so a revoked tenant stops working at once.\n const app = await services.widgetService.resolveDataAppName(undefined, reference.embedId);\n if (!app.ok) throw new Error('Chart scope unavailable');\n const scope = await services.widgetService.ensureReadableDashboardScope({\n embedId: reference.embedId, clientId: reference.clientId, dataAppName: app.value });\n if (!scope.ok) throw new Error('Chart scope denied');\n const { token } = await services.guestTokenService.generateToken({\n clientId: reference.clientId, dataAppName: app.value, allowedEmbeds: [reference.embedId],\n permissions: { isDisableMetricCreation: true, isEnableManageMetrics: false,\n isEnableMetricUpdation: false, isEnableArchiveMetrics: false },\n });\n if (!token) throw new Error('Guest scope unavailable');\n const page = await client.request<unknown>({\n method: 'POST', path: RENDER_PATH, auth: 'none', token,\n body: { action, embedId: reference.embedId, widgetId: reference.widgetId, ...view,\n pageSize: 50, width: 600, height: 320, animation: false },\n skipDataUnwrap: true,\n });\n return pageSchema.parse(page);\n },\n };\n}\n","/** Deliberately no imports in the iframe. All requests go through the host bridge. */\nexport const TILE_SCRIPT = String.raw`(()=>{\nconst $=id=>document.getElementById(id),root=document.documentElement,pending=new Map();\nlet seq=0,origin='*',ready=false,closed=false,ref=null,view={},next=null,context={},busy=false,revision=0,rawSvg=null,dialect=null,shown=false;\n/* One lifecycle, two sets of names. Hosts ship either or both, so notifications go out\n under both and arrive under either. Same order in both rows: result, input, context. */\nconst DASH=['ui/notifications/tool-result','ui/notifications/tool-input','ui/notifications/host-context-changed'],\nSNAKE=['notifications/tools/call/result','notifications/tools/call/input','notifications/host_context/changed'],\nINIT=['ui/notifications/initialized','notifications/initialized'],\nSIZE=['ui/notifications/size-changed','notifications/size_changed'],\nDISPLAY=['ui/request-display-mode','requestDisplayMode'];\nfunction send(message){if(!closed)parent.postMessage({jsonrpc:'2.0',...message},origin)}\nfunction notify(names,params){for(const method of names)send({method,params})}\nfunction rpc(method,params){return new Promise((resolve,reject)=>{const id=++seq;\nconst timer=setTimeout(()=>{pending.delete(id);reject(Error('timeout'))},30000);\npending.set(id,{resolve,reject,timer});send({id,method,params})})}\n/* Try each alias while the host says it never heard of it; any other error stands. */\nasync function ask(names,params){let last;\nfor(const method of names){try{return await rpc(method,params)}catch(error){last=error;if(error.code!==-32601)throw error}}\nthrow last}\n/* Fixed code only. The tile sits inside customer rows; a log is no place to leak them. */\nfunction trouble(code){send({method:'notifications/error',params:{code}})}\nfunction status(text,bad){const s=$('status');s.textContent=text;s.toggleAttribute('data-error',!!bad);if(bad)$('plot').dataset.state='off'}\nfunction paint(){const img=$('chart-image');if(!img||!rawSvg)return;\nconst c=getComputedStyle(document.body).color,mix=pc=>'color-mix(in srgb,'+c+' '+pc+'%,transparent)';\nimg.src='data:image/svg+xml,'+encodeURIComponent(rawSvg.replace(/<\\/svg>\\s*$/i,'<style>text{fill:'+c+'!important}'\n+'[stroke=\"#dbdee4\"]{stroke:'+mix(13)+'!important}[stroke=\"#54555a\"]{stroke:'+mix(30)+'!important}'\n+'</style></svg>'))}\nfunction controls(){document.querySelectorAll('button,input,select').forEach(e=>{e.disabled=busy});\n$('refresh').hidden=!ref;$('next').hidden=!next;$('range').hidden=!ref;$('plot').toggleAttribute('data-busy',busy);\n$('expand').hidden=!shown||!context.availableDisplayModes?.includes('fullscreen');\nroot.toggleAttribute('data-deep',context.displayMode==='fullscreen'||$('expand').hidden)}\nfunction host(change){context={...context,...change};if(change.theme==='dark'||change.theme==='light')root.style.colorScheme=change.theme;\nconst vars=change.styles?.variables||{};for(const [key,value] of Object.entries(vars)){\nif(/^--(?:color-|font-|border-|shadow-)/.test(key)&&typeof value==='string'&&!/url\\s*\\(|@import/i.test(value))root.style.setProperty(key,value)}\nconst face=change.styles?.css?.fonts;if(typeof face==='string'&&face.length<4000&&!/@import|javascript:/i.test(face))$('hostfont').textContent=face;\nconst insets=change.safeAreaInsets;if(insets)for(const edge of ['top','right','bottom','left']){\nconst n=insets[edge];if(Number.isFinite(n))root.style.setProperty('--safe-'+edge,Math.max(0,Math.min(n,200))+'px')}\nroot.dataset.mode=context.displayMode==='fullscreen'?'full':'inline';\n$('expand').textContent=context.displayMode==='fullscreen'?'Back to conversation':'Expand';paint();controls()}\nfunction scope(p,n){const bits=[];\nif(p.kind==='chart'||p.kind==='table')bits.push(n?(n===1?'1 row':n+' rows'):'No rows in this view');\nif(view.dateRange)bits.push(view.dateRange.from+' to '+view.dateRange.to);\nif(next)bits.push('more pages available');\n$('scope').textContent=bits.join(' · ')}\nfunction resize(){if(ready)notify(SIZE,{width:document.body.scrollWidth,height:document.body.scrollHeight})}\nfunction table(rows,columns){$('rows').replaceChildren();$('data').hidden=!rows?.length;if(!rows?.length)return;\n$('count').textContent=rows.length===1?'1 row behind this':rows.length+' rows behind this';\nconst t=document.createElement('table');\nconst num=name=>rows.some(r=>typeof r[name]==='number');\nconst header=document.createElement('tr');for(const name of columns){const th=document.createElement('th');th.scope='col';th.textContent=name;if(num(name))th.className='n';header.append(th)}\nconst head=document.createElement('thead');head.append(header);t.append(head);const body=document.createElement('tbody');\nfor(const row of rows.slice(0,50)){const tr=document.createElement('tr');for(const key of columns){const td=document.createElement('td');td.textContent=String(row[key]??'');if(num(key))td.className='n';tr.append(td)}body.append(tr)}t.append(body);$('rows').append(t)}\nasync function display(mode){try{const answer=await ask(DISPLAY,{mode});\nhost({displayMode:answer.mode||context.displayMode});if(root.dataset.mode==='full'&&!$('data').hidden)$('data').open=true;resize()}\ncatch{status('Expanded view is unavailable in this host.',1)}}\nfunction wait(){busy=true;if(!$('plot').firstChild)$('plot').dataset.state='wait';status('Loading chart…');controls()}\nasync function call(name,extra={}){if(!ready||!ref||busy)return;wait();const rev=++revision;\ntry{const result=await rpc('tools/call',{name,arguments:{reference:ref,view:{...view,...extra},context:'User is interacting with the chart tile.'}});\nif(rev===revision)render(result)}catch{if(rev===revision){trouble('tool-call-failed');status('Could not load the chart. Select Refresh to try again.',1)}}finally{busy=false;controls()}}\nfunction render(result){busy=false;controls();\nif(result?.isError){trouble('tool-returned-error');shown=false;controls();\nstatus(ref?'Chart unavailable. Check your access to this dashboard, then select Refresh.'\n:'Chart unavailable. Ask for this chart again to retry.',1);return}\nconst p=result?.structuredContent;if(!p||typeof p!=='object'){shown=false;controls();status('No chart data came back for this result.',1);return}\nrevision++;shown=true;rawSvg=null;$('plot').replaceChildren();$('plot').dataset.state='ready';$('choices').replaceChildren();delete $('rail').dataset.value;\nref=p.reference||null;view=p.view||{};next=p.nextCursor||null;\n$('title').textContent=p.title||'Analytics';$('description').textContent=p.description||'';\nif(p.kind==='dashboard'){$('plot').dataset.state='off';\nfor(const item of p.widgets||[]){const b=document.createElement('button');b.textContent=item.name||'Untitled widget';b.onclick=()=>{ref=item.reference;view={};call('render_chart')};$('choices').append(b)}\nstatus(p.widgets?.length?'':'This dashboard has no charts yet. Add one in Databrain, then ask again.');\n}else if(p.kind==='kpi'){\nconst number=document.createElement('p');number.className='kpi';number.textContent=String(p.value??'—');$('plot').append(number);status('');\n}else if(p.svg){\nconst box=document.createElement('div');box.className='chart';box.style.aspectRatio=p.width+'/'+p.height;\nconst img=document.createElement('img');img.id='chart-image';rawSvg=p.svg;img.alt=p.title||'Chart';img.onerror=()=>{status('The chart image could not be drawn. The rows behind it are below.',1);$('data').open=$('data').toggleAttribute('data-only',true)};box.append(img);\nfor(const point of p.points||[]){const b=document.createElement('button');b.className='point';b.setAttribute('aria-label',point.label+': '+point.value);b.setAttribute('aria-pressed','false');\nb.style.left=100*point.x/p.width+'%';b.style.top=100*point.y/p.height+'%';b.style.width=100*point.width/p.width+'%';b.style.height=100*point.height/p.height+'%';\nconst hover=()=>{$('vlabel').textContent=point.label;$('vnum').textContent=point.value;$('rail').dataset.value='1'};b.onmouseenter=hover;b.onfocus=hover;\nb.onclick=()=>{box.querySelectorAll('.point').forEach(e=>e.setAttribute('aria-pressed','false'));b.setAttribute('aria-pressed','true');hover();\n$('choices').replaceChildren();if(point.drillId){const drill=document.createElement('button');drill.className='primary';drill.textContent='Explore selected data';drill.onclick=()=>call('drill_down',{drillId:point.drillId,cursor:undefined});$('choices').append(drill)}};box.append(b)}$('plot').append(box);\nstatus('');\n}else if(p.kind==='table'){\n// ask_question returns a table by design; calling that a missing chart called success a failure.\n$('plot').dataset.state='off';status('');\n}else{$('plot').dataset.state='off';\nstatus('Nothing to show for this view. Try a wider date range, then select Refresh.',1)}\npaint();table(p.rows||[],p.columns||Object.keys(p.rows?.[0]||{}));scope(p,p.rows?.length||0);\nconst only=!p.svg&&p.value===undefined&&p.kind!=='dashboard';$('data').open=only;$('data').toggleAttribute('data-only',only);controls();resize()}\n$('refresh').onclick=()=>call('refresh_widget',{cursor:undefined});$('next').onclick=()=>call('render_chart',{cursor:next});\n$('expand').onclick=()=>display(context.displayMode==='fullscreen'?'inline':'fullscreen');\n$('dates').onclick=()=>{const from=$('from').value,to=$('to').value;if(!from||!to||from>to){status('Choose a start date on or before the end date.',1);return}call('change_date_range',{dateRange:{from,to},cursor:undefined,drillId:undefined})};\nfunction receive(event){if(event.source!==parent||closed)return;const m=event.data;\nif(!m||Array.isArray(m)||m.jsonrpc!=='2.0'||(origin!=='*'&&event.origin!==origin))return;\nif(!m.method&&pending.has(m.id)&&('result'in m||'error'in m)){if(origin==='*'&&event.origin!=='null')origin=event.origin;\nconst p=pending.get(m.id);pending.delete(m.id);clearTimeout(p.timer);\nif(m.error){const error=Error('host error');error.code=m.error.code;p.reject(error)}else p.resolve(m.result);return}\nif(m.method==='ping'&&m.id!==undefined){send({id:m.id,result:{}});return}\nif(m.method==='ui/resource-teardown'){send({id:m.id,result:{}});closed=true;revision++;for(const p of pending.values()){clearTimeout(p.timer);p.reject(Error('closed'))}pending.clear();observer.disconnect();removeEventListener('message',receive);return}\nif(!ready||m.id!==undefined)return;\nlet slot=DASH.indexOf(m.method),kind='dashed';\nif(slot<0){slot=SNAKE.indexOf(m.method);kind='snake'}\nif(slot>=0){\n/* A dual host posts each notification twice. Lock to the first name and drop its twin:\n same payload, and a second render is a visible flicker. */\nif(!dialect)dialect=kind;if(dialect!==kind)return;\n// The snake result nests one level deeper.\nif(slot===0)render(kind==='snake'?m.params&&m.params.result:m.params);\nelse if(slot===1)wait();\nelse{host(m.params||{});resize()}\nreturn}\nif(m.method==='ui/notifications/tool-cancelled'){revision++;busy=false;$('plot').dataset.state='off';status('Chart request cancelled.');controls()}}\naddEventListener('message',receive);const observer=new ResizeObserver(resize);observer.observe(document.body);controls();\nrpc('ui/initialize',{protocolVersion:'2026-01-26',appInfo:{name:'Databrain chart tile',version:'1.0.0'},appCapabilities:{availableDisplayModes:['inline','fullscreen']}})\n.then(r=>{if(r.protocolVersion!=='2026-01-26')throw Error('version');host(r.hostContext||{});ready=true;notify(INIT,{});resize()})\n.catch(()=>{trouble('initialize-failed');status('This chart needs a host that supports MCP Apps.',1)});\n})();`;\n\nexport function tileHtml(kind: 'chart' | 'kpi' = 'chart'): string {\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'; base-uri 'none'; form-action 'none'\">\n<title>Databrain ${kind} tile</title><style id=\"hostfont\"></style><style>\n/* Host tokens are the Claude MCP Apps style-variable names. Every one carries a fallback:\n hosts may ship any subset, so nothing here may depend on a token existing. */\n:root{color-scheme:light dark;\n--dbn-bg:var(--color-background-primary,Canvas);--dbn-text:var(--color-text-primary,CanvasText);\n--dbn-mute:var(--color-text-secondary,GrayText);--dbn-faint:var(--color-text-tertiary,GrayText);\n--dbn-edge:var(--color-border-secondary,color-mix(in srgb,currentColor 20%,transparent));\n--dbn-line:var(--color-border-tertiary,color-mix(in srgb,currentColor 9%,transparent));\n--dbn-accent:var(--color-text-info,LinkText);--dbn-ring:var(--color-ring-primary,var(--dbn-accent));\n--dbn-bad:var(--color-text-danger,var(--dbn-text));--dbn-bad-edge:var(--color-border-danger,currentColor);\n--dbn-quiet:color-mix(in srgb,currentColor 6%,transparent);\n--bw:var(--border-width-regular,1px);--r-s:var(--border-radius-sm,6px);--r-m:var(--border-radius-md,8px);--r-l:var(--border-radius-lg,10px);\n--med:var(--font-weight-medium,500);--semi:var(--font-weight-semibold,600);\n/* One curve. Quick / standard / display-mode. Corporate: no overshoot. */\n--e:cubic-bezier(.2,0,0,1);--q:120ms;--s:300ms;--l:400ms}\n*{box-sizing:border-box}\nbody{margin:0;padding:var(--safe-top,0px) var(--safe-right,0px) var(--safe-bottom,0px) var(--safe-left,0px);background:var(--dbn-bg);color:var(--dbn-text);\nfont:var(--font-text-sm-size,14px)/var(--font-text-sm-line-height,1.4) var(--font-sans,system-ui)}\nmain{padding:20px;max-width:1200px;margin:auto;transition:padding var(--l) var(--e)}\nheader{display:flex;align-items:start;justify-content:space-between;gap:12px}header>div{min-width:0}\nh1{font-size:var(--font-heading-md-size,18px);line-height:var(--font-heading-md-line-height,1.4);margin:0 0 6px;font-weight:var(--semi);letter-spacing:-.01em;overflow-wrap:anywhere;transition:font-size var(--l) var(--e)}\np{margin:6px 0;color:var(--dbn-mute);overflow-wrap:anywhere}#expand{flex:none}\nbutton,input{font:inherit;color:inherit;background:var(--dbn-bg);border:var(--bw) solid var(--dbn-edge);border-radius:var(--r-s);padding:7px 10px}\nbutton{cursor:pointer;transition:background-color var(--q) var(--e),border-color var(--q) var(--e)}\ninput{max-width:160px;min-width:0}button:hover:not(:disabled){background:var(--dbn-quiet)}button:disabled{color:var(--dbn-faint);cursor:default}\nbutton:focus-visible,input:focus-visible,summary:focus-visible{outline:2px solid var(--dbn-ring);outline-offset:2px}\n#choices button{text-align:left;border-color:transparent;border-bottom-color:var(--dbn-line);border-radius:0;padding:10px 2px}\n#choices button:first-child{border-top:var(--bw) solid var(--dbn-line)}\n#choices button:hover:not(:disabled){background:var(--dbn-quiet);border-radius:var(--r-s)}\n/* One primary per state: the drill pill, and Next page when there is a next page. */\n#choices button.primary,#next{flex:none;text-align:center;border:var(--bw) solid var(--dbn-accent);\nborder-radius:var(--r-s);padding:7px 10px;font-weight:var(--med);color:var(--dbn-accent)}\n#choices button.primary{align-self:flex-start;margin-top:10px}\n/* Refresh is a re-fetch, not a choice. Quiet it so it stops reading as a fifth chart. */\n#refresh{border-color:transparent;color:var(--dbn-mute);padding-left:2px;padding-right:8px}\n#refresh:hover:not(:disabled){color:var(--dbn-text);background:none;text-decoration:underline}\n#status:empty{margin:0}\n#status[data-error]{color:var(--dbn-bad);font-weight:var(--med);\nborder-left:2px solid var(--color-border-danger,color-mix(in srgb,currentColor 40%,transparent));\nbackground:var(--color-background-danger,color-mix(in srgb,currentColor 5%,transparent));\nborder-radius:0 var(--r-m) var(--r-m) 0;padding:10px 12px;margin:10px 0}\n/* Reserved plot. The skeleton is a static bar silhouette matching the 15:8 backend render:\n Claude's guidance asks for a skeleton that matches the final layout, and bans nothing here,\n but shimmer is banned by our own motion budget - so this paints once and never repaints. */\n#plot{position:relative;transition:opacity var(--q) var(--e)}#plot[data-busy]{opacity:.55}\n#plot[data-state=wait]{aspect-ratio:15/8}\n#plot[data-state=wait]::before,#plot[data-state=ready]::before{content:'';position:absolute;pointer-events:none;inset:16% 6% 13%;\nborder-bottom:var(--bw) solid var(--dbn-line);transition:opacity var(--s) var(--e);\nbackground:linear-gradient(var(--dbn-quiet),var(--dbn-quiet)) 0 100%/11% 44% no-repeat,\nlinear-gradient(var(--dbn-quiet),var(--dbn-quiet)) 22% 100%/11% 71% no-repeat,\nlinear-gradient(var(--dbn-quiet),var(--dbn-quiet)) 44% 100%/11% 57% no-repeat,\nlinear-gradient(var(--dbn-quiet),var(--dbn-quiet)) 66% 100%/11% 86% no-repeat,\nlinear-gradient(var(--dbn-quiet),var(--dbn-quiet)) 88% 100%/11% 33% no-repeat}\n#plot[data-state=ready]::before{opacity:0}\n.chart{position:relative;width:100%;margin:12px 0}.chart img{width:100%;height:100%;display:block}\n.point{position:absolute;padding:0;border:0;background:transparent;border-radius:var(--border-radius-xs,4px);\noutline:2px solid transparent;outline-offset:1px;transition:background-color var(--q) var(--e),outline-color var(--q) var(--e)}\n.point:hover{background:color-mix(in srgb,currentColor 12%,transparent)}\n.point[aria-pressed=true]{outline-color:var(--dbn-accent);background:color-mix(in srgb,var(--dbn-accent) 18%,transparent)}\n.kpi{font-size:clamp(34px,10vw,44px);line-height:1.1;font-weight:var(--semi);letter-spacing:-.025em;font-variant-numeric:tabular-nums;color:var(--dbn-text);margin:14px 0 6px;transition:font-size var(--l) var(--e)}\n/* The readout rail. At rest it states the scope of what is on screen; on selection it\n becomes the value. One slot, two jobs, so selecting a bar never reflows the tile. */\n#rail{display:flex;align-items:center;min-height:2.5em;margin:2px 0 0}\n#scope,#value{margin:0;display:none;overflow-wrap:anywhere}\n#rail:not([data-value]) #scope{display:block;color:var(--dbn-mute);font-size:var(--font-text-xs-size,12px)}\n#rail:not([data-value]):has(#scope:empty){display:none}\n#rail[data-value] #value{display:flex;flex-direction:column;gap:1px;line-height:1.15}\n#vlabel{color:var(--dbn-mute);font-size:var(--font-text-xs-size,12px)}\n#vnum{font-size:var(--font-heading-md-size,18px);font-weight:var(--semi);letter-spacing:-.015em;font-variant-numeric:tabular-nums}\n[data-mode=full] #vnum{font-size:var(--font-heading-xl-size,24px)}\nfooter,.dates{display:flex;gap:8px;flex-wrap:wrap;align-items:center}\n#choices{display:flex;flex-direction:column;align-items:stretch;margin:10px 0 0}footer{margin-top:12px}.dates{margin:8px 0 2px}\nlabel{display:flex;align-items:center;gap:6px;color:var(--dbn-mute)}\n#range{margin-top:10px}#data{margin-top:2px}#data[hidden],#range[hidden]{display:none}\nhtml:not([data-deep]) #range,html:not([data-deep]) #data:not([data-only]){display:none}\nsummary{cursor:pointer;color:var(--dbn-mute);font-size:var(--font-text-xs-size,12px);padding:5px 0;width:fit-content;transition:color var(--q) var(--e)}summary:hover{color:var(--dbn-text)}\n#rows{overflow:auto;max-height:260px;border-radius:var(--r-s)}\ntable{border-collapse:collapse;width:100%;font-size:var(--font-text-xs-size,12px)}\nth,td{text-align:left;padding:8px;border-bottom:var(--bw) solid var(--dbn-line);overflow-wrap:anywhere}\ntd.n,th.n{text-align:right;font-variant-numeric:tabular-nums}\nth{font-weight:var(--semi);position:sticky;top:0;background:var(--dbn-bg);color:var(--dbn-mute);\nfont-size:var(--font-text-xs-size,12px);letter-spacing:.02em;text-transform:uppercase}\n/* Inline to fullscreen is a layout morph, not a new screen: main widens over 400ms on the\n one curve and every dependent box follows it. Fullscreen is the detailed view, so the\n table loses its inline height cap. */\n/* The SVG is rendered at 600x320 and scaled by width, so an unbounded fullscreen column\n makes the chart's own axis type larger than the tile's heading. Hold a measure. */\n[data-mode=full] main{padding:28px 32px;max-width:900px}\n[data-mode=full] h1{font-size:var(--font-heading-xl-size,24px);line-height:var(--font-heading-xl-line-height,1.25)}\n[data-mode=full] .kpi{font-size:clamp(44px,7vw,72px)}\n[data-mode=full] #rows{max-height:none}\n/* Touch: the host gives vertical pan to the conversation, so a nested scroller is a trap. */\n@media(pointer:coarse){button,input{min-height:44px}summary{padding:12px 0}#rows{max-height:none;overflow:visible}}\n@media(max-width:480px){main{padding:14px}}\n/* Entrance. Content first, chrome 60ms later: 300+60 = 360ms, inside the 400ms budget.\n No stagger past two groups - a chat tile that cascades reads as lag, not luxury. */\n@keyframes rise{from{opacity:0;transform:translateY(6px)}}\n@keyframes land{from{opacity:0;transform:translateY(4px) scale(.995)}}\nheader,#status,#plot{animation:rise var(--s) var(--e) both}\n#rail,#choices,footer,#range,#data{animation:rise var(--s) var(--e) 60ms both}\n.chart,.kpi{animation:land var(--s) var(--e) both}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}\n</style></head><body><main id=\"tile\"><header><div><h1 id=\"title\">Analytics</h1><p id=\"description\"></p></div><button id=\"expand\" hidden>Expand</button></header>\n<p id=\"status\" role=\"status\">Loading chart…</p><div id=\"plot\" data-state=\"wait\"></div>\n<div id=\"rail\"><p id=\"scope\"></p><p id=\"value\"><span id=\"vlabel\"></span><b id=\"vnum\"></b></p></div><div id=\"choices\"></div>\n<footer><button id=\"refresh\" hidden>Refresh</button><button id=\"next\" hidden>Next page</button></footer>\n<details id=\"range\" hidden><summary>Change the date range</summary><div class=\"dates\"><label>From <input id=\"from\" type=\"date\"></label><label>To <input id=\"to\" type=\"date\"></label><button id=\"dates\" type=\"button\">Apply dates</button></div></details>\n<details id=\"data\" hidden><summary id=\"count\">Show the data behind this</summary><div id=\"rows\" tabindex=\"0\" role=\"region\" aria-label=\"Chart data\"></div></details></main><script>${TILE_SCRIPT}</script></body></html>`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;;;ACA1B,SAAS,SAAS;;;ACKX,IAAM,cAAc;AAM3B,IAAM,sBAAN,cAAkC,gBAAgB;AAAA,EAChD,YAA6B,UAA2B;AACtD,UAAM,EAAE,QAAQ,SAAS,UAAU,GAAG,YAAY,SAAS,cAAc,EAAE,CAAC;AADjD;AAAA,EAE7B;AAAA,EACS,QAAW,SAAqC;AACvD,WAAO,KAAK,SAAS,QAAW,EAAE,GAAG,SAAS,UAAU,QAAW,YAAY,OAAU,CAAC;AAAA,EAC5F;AACF;AAGO,SAAS,mBAAmB,QAAuC;AAExE,QAAM,WAAW,oBAAoB,IAAI,oBAAoB,MAAM,CAAC;AACpE,SAAO;AAAA,IACL,MAAM,QAAQ,QAAQ,cAAc,SAAS;AAC3C,YAAM,YAAY,gBAAgB,MAAM,YAAY;AACpD,YAAM,OAAO,WAAW,MAAM,OAAO;AAErC,YAAM,MAAM,MAAM,SAAS,cAAc,mBAAmB,QAAW,UAAU,OAAO;AACxF,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,yBAAyB;AACtD,YAAM,QAAQ,MAAM,SAAS,cAAc,6BAA6B;AAAA,QACtE,SAAS,UAAU;AAAA,QAAS,UAAU,UAAU;AAAA,QAAU,aAAa,IAAI;AAAA,MAAM,CAAC;AACpF,UAAI,CAAC,MAAM,GAAI,OAAM,IAAI,MAAM,oBAAoB;AACnD,YAAM,EAAE,MAAM,IAAI,MAAM,SAAS,kBAAkB,cAAc;AAAA,QAC/D,UAAU,UAAU;AAAA,QAAU,aAAa,IAAI;AAAA,QAAO,eAAe,CAAC,UAAU,OAAO;AAAA,QACvF,aAAa;AAAA,UAAE,yBAAyB;AAAA,UAAM,uBAAuB;AAAA,UACnE,wBAAwB;AAAA,UAAO,wBAAwB;AAAA,QAAM;AAAA,MACjE,CAAC;AACD,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,yBAAyB;AACrD,YAAM,OAAO,MAAM,OAAO,QAAiB;AAAA,QACzC,QAAQ;AAAA,QAAQ,MAAM;AAAA,QAAa,MAAM;AAAA,QAAQ;AAAA,QACjD,MAAM;AAAA,UAAE;AAAA,UAAQ,SAAS,UAAU;AAAA,UAAS,UAAU,UAAU;AAAA,UAAU,GAAG;AAAA,UAC3E,UAAU;AAAA,UAAI,OAAO;AAAA,UAAK,QAAQ;AAAA,UAAK,WAAW;AAAA,QAAM;AAAA,QAC1D,gBAAgB;AAAA,MAClB,CAAC;AACD,aAAO,WAAW,MAAM,IAAI;AAAA,IAC9B;AAAA,EACF;AACF;;;AChDO,IAAM,cAAc,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqH3B,SAAS,SAAS,OAAwB,SAAiB;AAChE,SAAO;AAAA;AAAA,mBAEU,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oLA2G6J,WAAW;AAC/L;;;AF3NA,IAAM,UAAuC;AAAA,EAC3C,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,mBAAmB;AACrB;AAGO,SAAS,kBAAkB,QAAmB,QAAyB,UAAwB,mBAAmB,MAAM,GAAS;AACtI,aAAW,CAAC,MAAM,WAAW,KAAK,OAAO,QAAQ,OAAO,GAAG;AACzD,UAAM,SAAS;AACf,UAAM,OAAO,OAAO,KAAK,MAAM,aAAa;AAAA,MAC1C,WAAW;AAAA,MAAiB,MAAM,WAAW,QAAQ,CAAC,CAAC;AAAA,MACvD,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,2BAA2B;AAAA,IAC9E,GAAG,WAAW,OAAO,EAAE,WAAW,KAAK,MAAM;AAC3C,UAAI;AACF,YAAK,WAAW,gBAAgB,CAAC,KAAK,WAAa,WAAW,uBAAuB,CAAC,KAAK,UAAY,OAAM,IAAI,MAAM,uBAAuB;AAC9I,cAAM,OAAO,WAAW,MAAM,MAAM,YAAY;AAAA,UAAI,EAAE,MAAM,MAAM,UAAU,aAAa,gBAAgB,aAAa,WAAW,MAAM;AAAA,UACrI,MAAM,QAAQ,QAAQ,QAAQ,WAAW,IAAI;AAAA,QAAC,CAAC;AAGjD,YAAI,KAAK,QAAQ,CAAC,cAAc,KAAK,KAAK,GAAG,KAC3C,kLAAkL,KAAK,KAAK,GAAG,IAAI;AACnM,gBAAM,IAAI,MAAM,YAAY;AAAA,QAC9B;AACA,eAAO,YAAY,GAAG,KAAK,SAAS,QAAQ,QAAQ,OAAO,WAAW,KAAK,KAAK,MAAM,uBAAuB;AAAA,UAC3G,GAAG;AAAA,UAAM;AAAA,UAAW;AAAA,QACtB,CAAC;AAAA,MACH,QAAQ;AACN,eAAO,EAAE,SAAS,MAAM,SAAS,CAAC;AAAA,UAAE,MAAM;AAAA,UACxC,MAAM;AAAA,QAA4F,CAAC,EAAE;AAAA,MACzG;AAAA,IACF,CAAC;AACD,SAAK,OAAO,EAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AAAA,EACzC;AACA,aAAW,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,WAAW,OAAO,GAAG,CAAC,SAAS,KAAK,CAAC,GAAY;AAC3E,WAAO,SAAS,aAAa,IAAI,SAAS,KAAK,EAAE,UAAU,UAAU,aAAa,eAAe,IAAI,4CAA4C,GAAG,aAAa;AAAA,MAC/J,UAAU,CAAC,EAAE,KAAK,UAAU,UAAU,MAAM,SAAS,IAAI,GAAG,OAAO,EAAE,IAAI,EAAE,eAAe,KAAK,EAAE,EAAE,CAAC;AAAA,IACtG,EAAE;AAAA,EACJ;AACF;;;ADtCO,SAAS,qBAAqB,QAAgB,SAAmC;AACtF,QAAM,SAAS,IAAI,gBAAgB,MAAM;AACzC,QAAM,SAAS,IAAI,UAAU,EAAE,MAAM,wBAAwB,SAAS,mBAAmB,GAAG;AAAA,IAC1F,cAAc;AAAA,EAEhB,CAAC;AACD,kBAAgB,MAAM;AACtB,mBAAiB,QAAQ,MAAM;AAC/B,uBAAqB,MAAM;AAC3B,qBAAmB,MAAM;AACzB,oBAAkB,QAAQ,QAAQ,OAAO;AACzC,SAAO;AACT;","names":[]}
|
|
@@ -24,7 +24,7 @@ var logger = {
|
|
|
24
24
|
// package.json
|
|
25
25
|
var package_default = {
|
|
26
26
|
name: "@databrainhq/mcp-server",
|
|
27
|
-
version: "0.3.0-rc.
|
|
27
|
+
version: "0.3.0-rc.3",
|
|
28
28
|
description: "MCP server for Databrain embedded analytics - connect databases, create datamarts, configure embeds, and generate frontend code",
|
|
29
29
|
type: "module",
|
|
30
30
|
bin: {
|
|
@@ -577,6 +577,7 @@ import { z as z2 } from "zod";
|
|
|
577
577
|
var CHART_URI = "ui://databrain/chart-tile";
|
|
578
578
|
var KPI_URI = "ui://databrain/kpi-tile";
|
|
579
579
|
var APP_MIME = "text/html;profile=mcp-app";
|
|
580
|
+
var RESOURCE_URI_META_KEY = "ui/resourceUri";
|
|
580
581
|
var PAGE_SIZE = 50;
|
|
581
582
|
var MAX_RESULT_CHARS = 125e3;
|
|
582
583
|
var id = z2.string().trim().min(1).max(256);
|
|
@@ -639,6 +640,9 @@ function firstRows(value) {
|
|
|
639
640
|
}
|
|
640
641
|
|
|
641
642
|
// src/apps/feature.ts
|
|
643
|
+
function appMeta(visibility) {
|
|
644
|
+
return { ui: { resourceUri: CHART_URI, visibility }, [RESOURCE_URI_META_KEY]: CHART_URI };
|
|
645
|
+
}
|
|
642
646
|
var enabled = /* @__PURE__ */ new WeakSet();
|
|
643
647
|
function enableChartApps(server) {
|
|
644
648
|
enabled.add(server);
|
|
@@ -647,10 +651,14 @@ function hasChartApps(server) {
|
|
|
647
651
|
return enabled.has(server);
|
|
648
652
|
}
|
|
649
653
|
function tagChartTool(server, tool) {
|
|
650
|
-
if (hasChartApps(server)) tool.update({ _meta:
|
|
654
|
+
if (hasChartApps(server)) tool.update({ _meta: appMeta(["model", "app"]) });
|
|
651
655
|
}
|
|
652
656
|
function chartResult(summary, payload) {
|
|
653
|
-
return bounded({
|
|
657
|
+
return bounded({
|
|
658
|
+
content: [{ type: "text", text: summary }],
|
|
659
|
+
structuredContent: payload,
|
|
660
|
+
_meta: appMeta(["model", "app"])
|
|
661
|
+
});
|
|
654
662
|
}
|
|
655
663
|
|
|
656
664
|
// src/tools/tool-metadata.ts
|
|
@@ -10445,10 +10453,11 @@ export {
|
|
|
10445
10453
|
referenceSchema,
|
|
10446
10454
|
viewSchema,
|
|
10447
10455
|
pageSchema,
|
|
10456
|
+
appMeta,
|
|
10448
10457
|
enableChartApps,
|
|
10449
10458
|
chartResult,
|
|
10450
10459
|
registerAllTools,
|
|
10451
10460
|
registerAllResources,
|
|
10452
10461
|
registerAllPrompts
|
|
10453
10462
|
};
|
|
10454
|
-
//# sourceMappingURL=chunk-
|
|
10463
|
+
//# sourceMappingURL=chunk-DZOYK5BB.js.map
|