drizzle-cube 0.4.49 → 0.4.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/adapters/express/index.cjs +1 -1
- package/dist/adapters/express/index.js +1 -1
- package/dist/adapters/fastify/index.cjs +1 -1
- package/dist/adapters/fastify/index.js +1 -1
- package/dist/adapters/hono/index.cjs +1 -1
- package/dist/adapters/hono/index.js +1 -1
- package/dist/adapters/mcp-tools.cjs +1 -1
- package/dist/adapters/mcp-tools.js +7 -2
- package/dist/adapters/{mcp-transport-BhPqU0Ft.js → mcp-transport-C6bsIOSV.js} +32 -14
- package/dist/adapters/{mcp-transport-Kk_HTCeR.cjs → mcp-transport-DMhuYmFp.cjs} +8 -4
- package/dist/adapters/mcp-transport.d.ts +4 -392
- package/dist/adapters/nextjs/index.cjs +1 -1
- package/dist/adapters/nextjs/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -174,7 +174,8 @@ Drizzle Cube includes a built-in **MCP server** that lets AI agents query your s
|
|
|
174
174
|
|------|---------|
|
|
175
175
|
| `drizzle_cube_discover` | Find relevant cubes by topic |
|
|
176
176
|
| `drizzle_cube_validate` | Validate queries with auto-corrections |
|
|
177
|
-
| `drizzle_cube_load` | Execute queries |
|
|
177
|
+
| `drizzle_cube_load` | Execute queries and return data |
|
|
178
|
+
| `drizzle_cube_chart` | Execute queries with interactive chart visualization |
|
|
178
179
|
|
|
179
180
|
### Connect AI Tools
|
|
180
181
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../utils-CyBt-as9.cjs`),t=require(`../compiler-CPE-YZfe.cjs`),n=require(`../mcp-transport-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../utils-CyBt-as9.cjs`),t=require(`../compiler-CPE-YZfe.cjs`),n=require(`../mcp-transport-DMhuYmFp.cjs`);let r=require(`express`);r=e._(r);let i=require(`cors`);i=e._(i);function a(a){let{cubes:o,drizzle:s,schema:c,extractSecurityContext:l,engineType:u,cors:d,basePath:f=`/cubejs-api/v1`,jsonLimit:p=`10mb`,cache:m,mcp:h={enabled:!0},agent:g}=a;if(!o||o.length===0)throw Error(`At least one cube must be provided in the cubes array`);let _=(0,r.Router)();d&&_.use((0,i.default)(d)),_.use(r.default.json({limit:p})),_.use(r.default.urlencoded({extended:!0,limit:p}));let v=new t.t({drizzle:s,schema:c,engineType:u,cache:m,rlsSetup:a.rlsSetup});if(o.forEach(e=>{v.registerCube(e)}),_.post(`${f}/load`,async(t,n)=>{try{let r=t.body.query||t.body,i=await l(t,n),a=v.validateQuery(r);if(!a.isValid)return n.status(400).json(e.i(`Query validation failed: ${a.errors.join(`, `)}`,400));let o=t.headers[`x-cache-control`]===`no-cache`,s=await v.executeMultiCubeQuery(r,i,{skipCache:o});n.json(e.r(r,s,v))}catch(t){console.error(`Query execution error:`,t),n.status(500).json(e.i(t instanceof Error?t.message:`Query execution failed`,500))}}),_.get(`${f}/load`,async(t,n)=>{try{let r=t.query.query;if(!r)return n.status(400).json(e.i(`Query parameter is required`,400));let i;try{i=JSON.parse(r)}catch{return n.status(400).json(e.i(`Invalid JSON in query parameter`,400))}let a=await l(t,n),o=v.validateQuery(i);if(!o.isValid)return n.status(400).json(e.i(`Query validation failed: ${o.errors.join(`, `)}`,400));let s=t.headers[`x-cache-control`]===`no-cache`,c=await v.executeMultiCubeQuery(i,a,{skipCache:s});n.json(e.r(i,c,v))}catch(t){console.error(`Query execution error:`,t),n.status(500).json(e.i(t instanceof Error?t.message:`Query execution failed`,500))}}),_.post(`${f}/batch`,async(t,n)=>{try{let{queries:r}=t.body;if(!r||!Array.isArray(r))return n.status(400).json(e.i(`Request body must contain a "queries" array`,400));if(r.length===0)return n.status(400).json(e.i(`Queries array cannot be empty`,400));let i=await e.u(r,await l(t,n),v,{skipCache:t.headers[`x-cache-control`]===`no-cache`});n.json(i)}catch(t){console.error(`Batch execution error:`,t),n.status(500).json(e.i(t instanceof Error?t.message:`Batch execution failed`,500))}}),_.get(`${f}/meta`,(t,n)=>{try{let t=v.getMetadata();n.json(e.a(t))}catch(t){console.error(`Metadata error:`,t),n.status(500).json(e.i(t instanceof Error?t.message:`Failed to fetch metadata`,500))}}),_.post(`${f}/sql`,async(t,n)=>{try{let r=t.body,i=await l(t,n),a=v.validateQuery(r);if(!a.isValid)return n.status(400).json(e.i(`Query validation failed: ${a.errors.join(`, `)}`,400));let o=r.measures?.[0]||r.dimensions?.[0];if(!o)return n.status(400).json(e.i(`No measures or dimensions specified`,400));let s=o.split(`.`)[0],c=await v.generateSQL(s,r,i);n.json(e.o(r,c))}catch(t){console.error(`SQL generation error:`,String(t).replace(/\n|\r/g,``)),n.status(500).json(e.i(t instanceof Error?t.message:`SQL generation failed`,500))}}),_.get(`${f}/sql`,async(t,n)=>{try{let r=t.query.query;if(!r)return n.status(400).json(e.i(`Query parameter is required`,400));let i=JSON.parse(r),a=await l(t,n),o=v.validateQuery(i);if(!o.isValid)return n.status(400).json(e.i(`Query validation failed: ${o.errors.join(`, `)}`,400));let s=i.measures?.[0]||i.dimensions?.[0];if(!s)return n.status(400).json(e.i(`No measures or dimensions specified`,400));let c=s.split(`.`)[0],u=await v.generateSQL(c,i,a);n.json(e.o(i,u))}catch(t){console.error(`SQL generation error:`,String(t).replace(/\n|\r/g,``)),n.status(500).json(e.i(t instanceof Error?t.message:`SQL generation failed`,500))}}),_.post(`${f}/dry-run`,async(t,n)=>{try{let r=await e.f(t.body.query||t.body,await l(t,n),v);n.json(r)}catch(e){console.error(`Dry-run error:`,e),n.status(400).json({error:e instanceof Error?e.message:`Dry-run validation failed`,valid:!1})}}),_.get(`${f}/dry-run`,async(t,n)=>{try{let r=t.query.query;if(!r)return n.status(400).json({error:`Query parameter is required`,valid:!1});let i=await e.f(JSON.parse(r),await l(t,n),v);n.json(i)}catch(e){console.error(`Dry-run error:`,e),n.status(400).json({error:e instanceof Error?e.message:`Dry-run validation failed`,valid:!1})}}),_.post(`${f}/explain`,async(e,t)=>{try{let n=e.body.query||e.body,r=e.body.options||{},i=await l(e,t),a=v.validateQuery(n);if(!a.isValid)return t.status(400).json({error:`Query validation failed: ${a.errors.join(`, `)}`});let o=await v.explainQuery(n,i,r);t.json(o)}catch(e){console.error(`Explain error:`,e),t.status(500).json({error:e instanceof Error?e.message:`Explain query failed`})}}),g&&_.post(`${f}/agent/chat`,async(e,t)=>{try{let{handleAgentChat:n}=await Promise.resolve().then(()=>require(`../handler-DumFgnNM.cjs`)),{message:r,sessionId:i,history:a}=e.body;if(!r||typeof r!=`string`)return t.status(400).json({error:`message is required and must be a string`});let o=(g.apiKey||``).trim();if(g.allowClientApiKey){let t=e.headers[`x-agent-api-key`];t&&(o=t.trim())}if(!o)return t.status(401).json({error:`No API key configured. Set agent.apiKey in server config or send X-Agent-Api-Key header.`});let s=g.allowClientApiKey?e.headers[`x-agent-provider`]:void 0,c=g.allowClientApiKey?e.headers[`x-agent-model`]:void 0,u=g.allowClientApiKey?e.headers[`x-agent-provider-endpoint`]:void 0,d=await l(e,t),f=g.buildSystemContext?.(d);t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`});try{let e=n({message:r,sessionId:i,history:a,semanticLayer:v,securityContext:d,agentConfig:g,apiKey:o,systemContext:f,providerOverride:s,modelOverride:c,baseURLOverride:u});for await(let n of e)t.write(`data: ${JSON.stringify(n)}\n\n`)}catch(e){let n={type:`error`,data:{message:e instanceof Error?e.message:`Stream failed`}};t.write(`data: ${JSON.stringify(n)}\n\n`)}finally{t.end()}}catch(e){console.error(`Agent chat error:`,e),t.headersSent||t.status(500).json({error:e instanceof Error?e.message:`Agent chat failed`})}}),h.enabled!==!1){let e=h.basePath??`/mcp`;_.post(`${e}`,async(e,t)=>{let r=n._(e.headers.origin,h.allowedOrigins?{allowedOrigins:h.allowedOrigins}:{});if(!r.valid)return t.status(403).json(n.i(null,-32600,r.reason));let i=e.headers.accept;if(!n.g(i))return t.status(400).json(n.i(null,-32600,`Accept header must include both application/json and text/event-stream`));let a=n.f(e.headers);if(!a.ok)return t.status(426).json({error:`Unsupported MCP protocol version`,supported:a.supported});let o=n.p(e.body);if(!o)return t.status(400).json(n.i(null,-32600,`Invalid JSON-RPC 2.0 request`));let s=n.v(i),c=o.method===`initialize`;try{let r=await n.s(o.method,o.params,{semanticLayer:v,extractSecurityContext:l,rawRequest:e,rawResponse:t,negotiatedProtocol:a.negotiated,appEnabled:!!h.app});if(n.d(o))return t.status(202).end();let i=c&&r&&typeof r==`object`&&`sessionId`in r?r.sessionId:void 0;i&&t.setHeader(n.r,i);let u=n.a(o.id??null,r);if(s){let e=n.m();return t.status(200),t.setHeader(`Content-Type`,`text/event-stream`),t.setHeader(`Cache-Control`,`no-cache`),t.setHeader(`Connection`,`keep-alive`),t.write(`id: ${e}\n\n`),t.write(n.h(u,e)),t.end()}return t.json(u)}catch(e){if(n.d(o))return console.error(`MCP notification processing error:`,String(e).replace(/\n|\r/g,``)),t.status(202).end();console.error(`MCP RPC error:`,String(e).replace(/\n|\r/g,``));let r=e?.code??-32603,i=e?.data,a=e.message||`MCP request failed`,c=n.i(o.id??null,r,a,i);if(s){let e=n.m();return t.status(200),t.setHeader(`Content-Type`,`text/event-stream`),t.setHeader(`Cache-Control`,`no-cache`),t.setHeader(`Connection`,`keep-alive`),t.write(`id: ${e}\n\n`),t.write(n.h(c,e)),t.end()}return t.status(200).json(c)}}),_.get(`${e}`,async(e,t)=>{let r=n.m();t.status(200),t.setHeader(`Content-Type`,`text/event-stream`),t.setHeader(`Cache-Control`,`no-cache`),t.setHeader(`Connection`,`keep-alive`),t.write(n.h({jsonrpc:`2.0`,method:`mcp/ready`,params:{protocol:`streamable-http`}},r,15e3));let i=setInterval(()=>{t.write(`: keep-alive
|
|
2
2
|
|
|
3
3
|
`)},15e3);e.on(`close`,()=>{clearInterval(i)})}),_.delete(`${e}`,(e,t)=>t.status(405).json({error:`Session termination not supported`}))}return _.use((t,n,r,i)=>{console.error(`Express adapter error:`,t),r.headersSent||r.status(500).json(e.i(t,500))}),_}function o(e,t){let n=a(t);return e.use(`/`,n),e}function s(e){return o((0,r.default)(),e)}exports.createCubeApp=s,exports.createCubeRouter=a,exports.mountCubeRoutes=o;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as e, f as t, i as n, o as r, r as i, u as a } from "../utils-IH1ePsBd.js";
|
|
2
2
|
import { t as o } from "../compiler-BXfrvebp.js";
|
|
3
|
-
import { _ as s, a as c, d as l, f as u, g as d, h as f, i as p, m, p as h, r as g, s as _, v } from "../mcp-transport-
|
|
3
|
+
import { _ as s, a as c, d as l, f as u, g as d, h as f, i as p, m, p as h, r as g, s as _, v } from "../mcp-transport-C6bsIOSV.js";
|
|
4
4
|
import y, { Router as b } from "express";
|
|
5
5
|
import x from "cors";
|
|
6
6
|
//#region src/adapters/express/index.ts
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../utils-CyBt-as9.cjs`),t=require(`../compiler-CPE-YZfe.cjs`),n=require(`../mcp-transport-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../utils-CyBt-as9.cjs`),t=require(`../compiler-CPE-YZfe.cjs`),n=require(`../mcp-transport-DMhuYmFp.cjs`);var r=function(r,i,a){let{cubes:o,drizzle:s,schema:c,extractSecurityContext:l,engineType:u,cors:d,basePath:f=`/cubejs-api/v1`,bodyLimit:p=10485760,cache:m,mcp:h={enabled:!0},agent:g}=i;if(!o||o.length===0)return a(Error(`At least one cube must be provided in the cubes array`));d&&r.register(import(`@fastify/cors`),d),r.addHook(`onRequest`,async(e,t)=>{e.method===`POST`&&(e.body=void 0)});let _=new t.t({drizzle:s,schema:c,engineType:u,cache:m,rlsSetup:i.rlsSetup});if(o.forEach(e=>{_.registerCube(e)}),r.post(`${f}/load`,{bodyLimit:p,schema:{body:{type:`object`,additionalProperties:!0}}},async(t,n)=>{try{let r=t.body,i=r.query||r,a=await l(t),o=_.validateQuery(i);if(!o.isValid)return n.status(400).send(e.i(`Query validation failed: ${o.errors.join(`, `)}`,400));let s=t.headers[`x-cache-control`]===`no-cache`;return e.r(i,await _.executeMultiCubeQuery(i,a,{skipCache:s}),_)}catch(r){return t.log.error(r,`Query execution error`),n.status(500).send(e.i(r instanceof Error?r.message:`Query execution failed`,500))}}),r.get(`${f}/load`,{schema:{querystring:{type:`object`,properties:{query:{type:`string`}},required:[`query`]}}},async(t,n)=>{try{let{query:r}=t.query,i;try{i=JSON.parse(r)}catch{return n.status(400).send(e.i(`Invalid JSON in query parameter`,400))}let a=await l(t),o=_.validateQuery(i);if(!o.isValid)return n.status(400).send(e.i(`Query validation failed: ${o.errors.join(`, `)}`,400));let s=t.headers[`x-cache-control`]===`no-cache`,c=await _.executeMultiCubeQuery(i,a,{skipCache:s});return e.r(i,c,_)}catch(r){return t.log.error(r,`Query execution error`),n.status(500).send(e.i(r instanceof Error?r.message:`Query execution failed`,500))}}),r.post(`${f}/batch`,{bodyLimit:p,schema:{body:{type:`object`,required:[`queries`],properties:{queries:{type:`array`,items:{type:`object`}}}}}},async(t,n)=>{try{let{queries:r}=t.body;return!r||!Array.isArray(r)?n.status(400).send(e.i(`Request body must contain a "queries" array`,400)):r.length===0?n.status(400).send(e.i(`Queries array cannot be empty`,400)):await e.u(r,await l(t),_,{skipCache:t.headers[`x-cache-control`]===`no-cache`})}catch(r){return t.log.error(r,`Batch execution error`),n.status(500).send(e.i(r instanceof Error?r.message:`Batch execution failed`,500))}}),r.get(`${f}/meta`,async(t,n)=>{try{return e.a(_.getMetadata())}catch(r){return t.log.error(r,`Metadata error`),n.status(500).send(e.i(r instanceof Error?r.message:`Failed to fetch metadata`,500))}}),r.post(`${f}/sql`,{bodyLimit:p,schema:{body:{type:`object`,additionalProperties:!0}}},async(t,n)=>{try{let r=t.body,i=await l(t),a=_.validateQuery(r);if(!a.isValid)return n.status(400).send(e.i(`Query validation failed: ${a.errors.join(`, `)}`,400));let o=r.measures?.[0]||r.dimensions?.[0];if(!o)return n.status(400).send(e.i(`No measures or dimensions specified`,400));let s=o.split(`.`)[0];return e.o(r,await _.generateSQL(s,r,i))}catch(r){return t.log.error({err:String(r).replace(/\n|\r/g,``)},`SQL generation error`),n.status(500).send(e.i(r instanceof Error?r.message:`SQL generation failed`,500))}}),r.get(`${f}/sql`,{schema:{querystring:{type:`object`,properties:{query:{type:`string`}},required:[`query`]}}},async(t,n)=>{try{let{query:r}=t.query,i=JSON.parse(r),a=await l(t),o=_.validateQuery(i);if(!o.isValid)return n.status(400).send(e.i(`Query validation failed: ${o.errors.join(`, `)}`,400));let s=i.measures?.[0]||i.dimensions?.[0];if(!s)return n.status(400).send(e.i(`No measures or dimensions specified`,400));let c=s.split(`.`)[0];return e.o(i,await _.generateSQL(c,i,a))}catch(r){return t.log.error({err:String(r).replace(/\n|\r/g,``)},`SQL generation error`),n.status(500).send(e.i(r instanceof Error?r.message:`SQL generation failed`,500))}}),r.post(`${f}/dry-run`,{bodyLimit:p,schema:{body:{type:`object`,additionalProperties:!0}}},async(t,n)=>{try{let n=t.body;return await e.f(n.query||n,await l(t),_)}catch(e){return t.log.error(e,`Dry-run error`),n.status(400).send({error:e instanceof Error?e.message:`Dry-run validation failed`,valid:!1})}}),r.get(`${f}/dry-run`,{schema:{querystring:{type:`object`,properties:{query:{type:`string`}},required:[`query`]}}},async(t,n)=>{try{let{query:n}=t.query;return await e.f(JSON.parse(n),await l(t),_)}catch(e){return t.log.error(e,`Dry-run error`),n.status(400).send({error:e instanceof Error?e.message:`Dry-run validation failed`,valid:!1})}}),r.post(`${f}/explain`,{bodyLimit:p,schema:{body:{type:`object`,additionalProperties:!0}}},async(e,t)=>{try{let n=e.body,r=n.query||n,i=n.options||{},a=await l(e),o=_.validateQuery(r);return o.isValid?await _.explainQuery(r,a,i):t.status(400).send({error:`Query validation failed: ${o.errors.join(`, `)}`})}catch(n){return e.log.error(n,`Explain error`),t.status(500).send({error:n instanceof Error?n.message:`Explain query failed`})}}),g&&r.post(`${f}/agent/chat`,{bodyLimit:p,schema:{body:{type:`object`,additionalProperties:!0}}},async(e,t)=>{try{let{handleAgentChat:n}=await Promise.resolve().then(()=>require(`../handler-DumFgnNM.cjs`)),{message:r,sessionId:i,history:a}=e.body;if(!r||typeof r!=`string`)return t.status(400).send({error:`message is required and must be a string`});let o=(g.apiKey||``).trim();if(g.allowClientApiKey){let t=e.headers[`x-agent-api-key`];t&&(o=t.trim())}if(!o)return t.status(401).send({error:`No API key configured. Set agent.apiKey in server config or send X-Agent-Api-Key header.`});let s=g.allowClientApiKey?e.headers[`x-agent-provider`]:void 0,c=g.allowClientApiKey?e.headers[`x-agent-model`]:void 0,u=g.allowClientApiKey?e.headers[`x-agent-provider-endpoint`]:void 0,d=await l(e),f=g.buildSystemContext?.(d);t.raw.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`});try{let e=n({message:r,sessionId:i,history:a,semanticLayer:_,securityContext:d,agentConfig:g,apiKey:o,systemContext:f,providerOverride:s,modelOverride:c,baseURLOverride:u});for await(let n of e)t.raw.write(`data: ${JSON.stringify(n)}\n\n`)}catch(e){let n={type:`error`,data:{message:e instanceof Error?e.message:`Stream failed`}};t.raw.write(`data: ${JSON.stringify(n)}\n\n`)}finally{t.raw.end()}}catch(n){if(e.log.error(n,`Agent chat error`),!t.raw.headersSent)return t.status(500).send({error:n instanceof Error?n.message:`Agent chat failed`})}}),h.enabled!==!1){let e=h.basePath??`/mcp`;r.post(`${e}`,{bodyLimit:p,schema:{body:{type:`object`,additionalProperties:!0}}},async(e,t)=>{let r=n._(e.headers.origin,h.allowedOrigins?{allowedOrigins:h.allowedOrigins}:{});if(!r.valid)return t.status(403).send(n.i(null,-32600,r.reason));let i=e.headers.accept;if(!n.g(i))return t.status(400).send(n.i(null,-32600,`Accept header must include both application/json and text/event-stream`));let a=n.f(e.headers);if(!a.ok)return t.status(426).send({error:`Unsupported MCP protocol version`,supported:a.supported});let o=n.p(e.body);if(!o)return t.status(400).send(n.i(null,-32600,`Invalid JSON-RPC 2.0 request`));let s=n.v(i),c=o.method===`initialize`;try{let r=await n.s(o.method,o.params,{semanticLayer:_,extractSecurityContext:l,rawRequest:e,rawResponse:t,negotiatedProtocol:a.negotiated,appEnabled:!!h.app});if(n.d(o))return t.status(202).send();let i=c&&r&&typeof r==`object`&&`sessionId`in r?r.sessionId:void 0;i&&t.header(n.r,i);let u=n.a(o.id??null,r);if(s){let e=n.m();t.header(`Content-Type`,`text/event-stream`).header(`Cache-Control`,`no-cache`).header(`Connection`,`keep-alive`).send(`id: ${e}\n\n${n.h(u,e)}`);return}return t.send(u)}catch(r){if(n.d(o))return e.log.error({err:String(r).replace(/\n|\r/g,``)},`MCP notification processing error`),t.status(202).send();e.log.error({err:String(r).replace(/\n|\r/g,``)},`MCP RPC error`);let i=r?.code??-32603,a=r?.data,c=r.message||`MCP request failed`,l=n.i(o.id??null,i,c,a);if(s){let e=n.m();t.header(`Content-Type`,`text/event-stream`).header(`Cache-Control`,`no-cache`).header(`Connection`,`keep-alive`).send(`id: ${e}\n\n${n.h(l,e)}`);return}return t.send(l)}}),r.get(`${e}`,async(e,t)=>{let r=n.m();t.raw.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.raw.write(n.h({jsonrpc:`2.0`,method:`mcp/ready`,params:{protocol:`streamable-http`}},r,15e3));let i=setInterval(()=>{t.raw.write(`: keep-alive
|
|
2
2
|
|
|
3
3
|
`)},15e3);e.raw.on(`close`,()=>{clearInterval(i)})}),r.delete(`${e}`,async(e,t)=>t.status(405).send({error:`Session termination not supported`}))}r.setErrorHandler(async(t,n,r)=>(n.log.error(t,`Fastify cube adapter error`),r.statusCode<400&&r.status(500),e.i(t instanceof Error?t:String(t),r.statusCode))),a()};async function i(e,t){await e.register(r,t)}function a(e){let t=require(`fastify`)({logger:!0});return t.register(r,e),t}exports.createCubeApp=a,exports.cubePlugin=r,exports.registerCubeRoutes=i;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _ as e, a as t, f as n, i as r, o as i, r as a, u as o } from "../utils-IH1ePsBd.js";
|
|
2
2
|
import { t as s } from "../compiler-BXfrvebp.js";
|
|
3
|
-
import { _ as c, a as l, d as u, f as d, g as f, h as p, i as m, m as h, p as g, r as _, s as v, v as y } from "../mcp-transport-
|
|
3
|
+
import { _ as c, a as l, d as u, f as d, g as f, h as p, i as m, m as h, p as g, r as _, s as v, v as y } from "../mcp-transport-C6bsIOSV.js";
|
|
4
4
|
//#region src/adapters/fastify/index.ts
|
|
5
5
|
var b = function(e, b, x) {
|
|
6
6
|
let { cubes: S, drizzle: C, schema: w, extractSecurityContext: T, engineType: E, cors: D, basePath: O = "/cubejs-api/v1", bodyLimit: k = 10485760, cache: A, mcp: j = { enabled: !0 }, agent: M } = b;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../utils-CyBt-as9.cjs`),t=require(`../compiler-CPE-YZfe.cjs`),n=require(`../mcp-transport-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../utils-CyBt-as9.cjs`),t=require(`../compiler-CPE-YZfe.cjs`),n=require(`../mcp-transport-DMhuYmFp.cjs`);let r=require(`hono`);var i=e=>{let t={origin:`*`,allowMethods:[`GET`,`HEAD`,`PUT`,`POST`,`DELETE`,`PATCH`],allowHeaders:[],exposeHeaders:[],...e},n=(e=>typeof e==`string`?e===`*`?t.credentials?e=>e||null:()=>e:t=>e===t?t:null:typeof e==`function`?e:t=>e.includes(t)?t:null)(t.origin),r=(e=>typeof e==`function`?e:Array.isArray(e)?()=>e:()=>[])(t.allowMethods);return async function(e,i){function a(t,n){e.res.headers.set(t,n)}let o=await n(e.req.header(`origin`)||``,e);if(o&&a(`Access-Control-Allow-Origin`,o),t.credentials&&a(`Access-Control-Allow-Credentials`,`true`),t.exposeHeaders?.length&&a(`Access-Control-Expose-Headers`,t.exposeHeaders.join(`,`)),e.req.method===`OPTIONS`){(t.origin!==`*`||t.credentials)&&a(`Vary`,`Origin`),t.maxAge!=null&&a(`Access-Control-Max-Age`,t.maxAge.toString());let n=await r(e.req.header(`origin`)||``,e);n.length&&a(`Access-Control-Allow-Methods`,n.join(`,`));let i=t.allowHeaders;if(!i?.length){let t=e.req.header(`Access-Control-Request-Headers`);t&&(i=t.split(/\s*,\s*/))}return i?.length&&(a(`Access-Control-Allow-Headers`,i.join(`,`)),e.res.headers.append(`Vary`,`Access-Control-Request-Headers`)),e.res.headers.delete(`Content-Length`),e.res.headers.delete(`Content-Type`),new Response(null,{headers:e.res.headers,status:204,statusText:`No Content`})}await i(),(t.origin!==`*`||t.credentials)&&e.header(`Vary`,`Origin`,{append:!0})}};function a(a){let{cubes:o,drizzle:s,schema:c,extractSecurityContext:l,engineType:u,cors:d,basePath:f=`/cubejs-api/v1`,cache:p,mcp:m={enabled:!0},agent:h}=a;if(!a.semanticLayer&&(!o||o.length===0))throw Error(`Either semanticLayer or a non-empty cubes array must be provided`);let g=new r.Hono;d&&g.use(`/*`,i(d));let _=a.semanticLayer??new t.t({drizzle:s,schema:c,engineType:u,cache:p,rlsSetup:a.rlsSetup});if(!a.semanticLayer&&o&&o.forEach(e=>{_.registerCube(e)}),g.post(`${f}/load`,async t=>{try{let n=await t.req.json(),r=n.query||n,i=await l(t),a=_.validateQuery(r);if(!a.isValid)return t.json({error:`Query validation failed: ${a.errors.join(`, `)}`},400);let o=t.req.header(`x-cache-control`)===`no-cache`,s=await _.executeMultiCubeQuery(r,i,{skipCache:o});return t.json(e.r(r,s,_))}catch(e){return console.error(`Query execution error:`,e),t.json({error:e instanceof Error?e.message:`Query execution failed`},500)}}),g.get(`${f}/load`,async t=>{try{let n=t.req.query(`query`);if(!n)return t.json({error:`Query parameter is required`},400);let r;try{r=JSON.parse(n)}catch{return t.json({error:`Invalid JSON in query parameter`},400)}let i=await l(t),a=_.validateQuery(r);if(!a.isValid)return t.json({error:`Query validation failed: ${a.errors.join(`, `)}`},400);let o=t.req.header(`x-cache-control`)===`no-cache`,s=await _.executeMultiCubeQuery(r,i,{skipCache:o});return t.json(e.r(r,s,_))}catch(e){return console.error(`Query execution error:`,e),t.json({error:e instanceof Error?e.message:`Query execution failed`},500)}}),g.post(`${f}/batch`,async t=>{try{let{queries:n}=await t.req.json();if(!n||!Array.isArray(n))return t.json({error:`Request body must contain a "queries" array`},400);if(n.length===0)return t.json({error:`Queries array cannot be empty`},400);let r=await e.u(n,await l(t),_,{skipCache:t.req.header(`x-cache-control`)===`no-cache`});return t.json(r)}catch(e){return console.error(`Batch execution error:`,e),t.json({error:e instanceof Error?e.message:`Batch execution failed`},500)}}),g.get(`${f}/meta`,t=>{try{let n=_.getMetadata();return t.json(e.a(n))}catch(e){return console.error(`Metadata error:`,e),t.json({error:e instanceof Error?e.message:`Failed to fetch metadata`},500)}}),g.post(`${f}/sql`,async t=>{try{let n=await t.req.json(),r=await l(t),i=_.validateQuery(n);if(!i.isValid)return t.json({error:`Query validation failed: ${i.errors.join(`, `)}`},400);let a=n.measures?.[0]||n.dimensions?.[0];if(!a)return t.json({error:`No measures or dimensions specified`},400);let o=a.split(`.`)[0],s=await _.generateSQL(o,n,r);return t.json(e.o(n,s))}catch(e){return console.error(`SQL generation error:`,e),t.json({error:e instanceof Error?e.message:`SQL generation failed`},500)}}),g.get(`${f}/sql`,async t=>{try{let n=t.req.query(`query`);if(!n)return t.json({error:`Query parameter is required`},400);let r=JSON.parse(n),i=await l(t),a=_.validateQuery(r);if(!a.isValid)return t.json({error:`Query validation failed: ${a.errors.join(`, `)}`},400);let o=r.measures?.[0]||r.dimensions?.[0];if(!o)return t.json({error:`No measures or dimensions specified`},400);let s=o.split(`.`)[0],c=await _.generateSQL(s,r,i);return t.json(e.o(r,c))}catch(e){return console.error(`SQL generation error:`,e),t.json({error:e instanceof Error?e.message:`SQL generation failed`},500)}}),g.post(`${f}/dry-run`,async t=>{try{let n=await t.req.json(),r=await e.f(n.query||n,await l(t),_);return t.json(r)}catch(e){return console.error(`Dry-run error:`,e),t.json({error:e instanceof Error?e.message:`Dry-run validation failed`,valid:!1},400)}}),g.get(`${f}/dry-run`,async t=>{try{let n=t.req.query(`query`);if(!n)return t.json({error:`Query parameter is required`,valid:!1},400);let r=await e.f(JSON.parse(n),await l(t),_);return t.json(r)}catch(e){return console.error(`Dry-run error:`,e),t.json({error:e instanceof Error?e.message:`Dry-run validation failed`,valid:!1},400)}}),g.post(`${f}/explain`,async e=>{try{let t=await e.req.json(),n=t.query||t,r=t.options||{},i=await l(e),a=_.validateQuery(n);if(!a.isValid)return e.json({error:`Query validation failed: ${a.errors.join(`, `)}`},400);let o=await _.explainQuery(n,i,r);return e.json(o)}catch(t){return console.error(`Explain error:`,t),e.json({error:t instanceof Error?t.message:`Explain query failed`},500)}}),h&&g.post(`${f}/agent/chat`,async e=>{try{let{handleAgentChat:t}=await Promise.resolve().then(()=>require(`../handler-DumFgnNM.cjs`)),{message:n,sessionId:r,history:i}=await e.req.json();if(!n||typeof n!=`string`)return e.json({error:`message is required and must be a string`},400);let a=(h.apiKey||``).trim();if(h.allowClientApiKey){let t=e.req.header(`x-agent-api-key`);t&&(a=t.trim())}if(!a)return e.json({error:`No API key configured. Set agent.apiKey in server config or send X-Agent-Api-Key header.`},401);let o=h.allowClientApiKey?e.req.header(`x-agent-provider`):void 0,s=h.allowClientApiKey?e.req.header(`x-agent-model`):void 0,c=h.allowClientApiKey?e.req.header(`x-agent-provider-endpoint`):void 0,u=await l(e),d=h.buildSystemContext?.(u),f=new TextEncoder,p=new ReadableStream({async start(e){try{let l=t({message:n,sessionId:r,history:i,semanticLayer:_,securityContext:u,agentConfig:h,apiKey:a,systemContext:d,providerOverride:o,modelOverride:s,baseURLOverride:c});for await(let t of l){let n=`data: ${JSON.stringify(t)}\n\n`;e.enqueue(f.encode(n))}}catch(t){let n={type:`error`,data:{message:t instanceof Error?t.message:`Stream failed`}};e.enqueue(f.encode(`data: ${JSON.stringify(n)}\n\n`))}finally{e.close()}}});return new Response(p,{status:200,headers:{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}})}catch(t){return console.error(`Agent chat error:`,t),e.json({error:t instanceof Error?t.message:`Agent chat failed`},500)}}),m.enabled!==!1){let e={uri:`drizzle-cube://schema`,name:`Cube Schema`,description:`Current cube metadata as JSON`,mimeType:`application/json`,text:JSON.stringify(_.getMetadata(),null,2)},t=[...n.l(),e],r=n.c(),i=m.basePath??`/mcp`;g.post(`${i}`,async e=>{let i=n._(e.req.header(`origin`),m.allowedOrigins?{allowedOrigins:m.allowedOrigins}:{});if(!i.valid)return e.json(n.i(null,-32600,i.reason),403);let a=e.req.header(`accept`);if(!n.g(a))return e.json(n.i(null,-32600,`Accept header must include both application/json and text/event-stream`),400);let o=n.f(e.req.header());if(!o.ok)return e.json({error:`Unsupported MCP protocol version`,supported:o.supported},426);let s=n.p(await e.req.json().catch(()=>null));if(!s)return e.json(n.i(null,-32600,`Invalid JSON-RPC 2.0 request`),400);let c=n.v(a),u=s.method===`initialize`;try{let i=await n.s(s.method,s.params,{semanticLayer:_,extractSecurityContext:l,rawRequest:e,rawResponse:null,negotiatedProtocol:o.negotiated,resources:t,prompts:r,appEnabled:!!m.app});if(n.d(s))return e.body(null,202);let a=n.a(s.id??null,i),d=u&&i&&typeof i==`object`&&`sessionId`in i?i.sessionId:void 0,f={};if(d&&(f[n.r]=d),c){let e=new TextEncoder,t=n.m(),r=new ReadableStream({start(r){r.enqueue(e.encode(`id: ${t}\n\n`)),r.enqueue(e.encode(n.h(a,t))),r.close()}});return new Response(r,{status:200,headers:{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`,...f}})}return e.json(a,200,f)}catch(t){if(n.d(s))return console.error(`MCP notification processing error:`,t),e.body(null,202);console.error(`MCP RPC error:`,t);let r=t?.code??-32603,i=t?.data,a=t.message||`MCP request failed`,o=n.i(s.id??null,r,a,i);if(c){let e=new TextEncoder,t=n.m(),r=new ReadableStream({start(r){r.enqueue(e.encode(`id: ${t}\n\n`)),r.enqueue(e.encode(n.h(o,t))),r.close()}});return new Response(r,{status:200,headers:{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}})}return e.json(o,200)}}),g.delete(`${i}`,e=>e.json({error:`Session termination not supported`},405)),g.get(`${i}`,e=>{let t=new TextEncoder,r=n.m(),i,a=new ReadableStream({start(e){e.enqueue(t.encode(n.h({jsonrpc:`2.0`,method:`mcp/ready`,params:{protocol:`streamable-http`}},r,15e3))),i=setInterval(()=>{e.enqueue(t.encode(`: keep-alive
|
|
2
2
|
|
|
3
3
|
`))},15e3)},cancel(){clearInterval(i)}});return new Response(a,{status:200,headers:{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}})})}return g}function o(e,t){let n=a(t);return e.route(`/`,n),e}function s(e){return o(new r.Hono,e)}exports.createCubeApp=s,exports.createCubeRoutes=a,exports.mountCubeRoutes=o;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as e, f as t, o as n, r, u as i } from "../utils-IH1ePsBd.js";
|
|
2
2
|
import { t as a } from "../compiler-BXfrvebp.js";
|
|
3
|
-
import { _ as o, a as s, c, d as l, f as u, g as d, h as f, i as p, l as m, m as h, p as g, r as _, s as v, v as y } from "../mcp-transport-
|
|
3
|
+
import { _ as o, a as s, c, d as l, f as u, g as d, h as f, i as p, l as m, m as h, p as g, r as _, s as v, v as y } from "../mcp-transport-C6bsIOSV.js";
|
|
4
4
|
import { Hono as b } from "hono";
|
|
5
5
|
//#region node_modules/hono/dist/middleware/cors/index.js
|
|
6
6
|
var x = (e) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./utils-CyBt-as9.cjs`),t=require(`./mcp-transport-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./utils-CyBt-as9.cjs`),t=require(`./mcp-transport-DMhuYmFp.cjs`);function n(e){return{content:[{type:`text`,text:typeof e==`string`?e:JSON.stringify(e)}],isError:!1}}function r(e){return{content:[{type:`text`,text:e instanceof Error?e.message:String(e)}],isError:!0}}function i(i){let{semanticLayer:o,getSecurityContext:s,toolPrefix:c=`drizzle_cube_`,tools:l=[`discover`,`validate`,`load`,`chart`],prompts:u=t.c(),resources:d,app:f=!1}=i,p=d??t.l(),m=f?[...p,...a()]:p,h=t.o({appEnabled:f}),g=new Map(h.map(e=>[e.name,e])),_=l.filter(e=>g.has(e)).map(e=>{let t=g.get(e),n={name:`${c}${e}`,description:t.description,inputSchema:t.inputSchema},r=t._meta;return r&&(n._meta=r),n}),v=_.map(e=>e.name),y=new Set;for(let e of l)y.add(e),y.add(`${c}${e}`);function b(e){return y.has(e)}async function x(t,i,a){let u=t.startsWith(c)?t.slice(c.length):t;if(!l.includes(u))return r(`Unknown tool: ${t}`);try{switch(u){case`discover`:return n(await e.d(o,i||{}));case`validate`:{let t=i||{};return t.query?n(await e.h(o,t)):r(`query is required`)}case`load`:{let t=i||{};return t.query?n(await e.p(o,await s(a),t)):r(`query is required`)}case`chart`:{let t=i||{};return t.query?n(await e.p(o,await s(a),t)):r(`query is required`)}default:return r(`Unknown tool: ${t}`)}}catch(e){return r(e)}}return{definitions:_,handle:x,handles:b,prompts:u,resources:m,toolNames:v}}function a(){let e=t.u();return e?[{uri:t.n,name:`Drizzle Cube Visualization`,description:`Interactive chart visualization for query results`,mimeType:t.t,text:e}]:[]}exports.getCubeTools=i;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as e, h as t, p as n } from "./utils-IH1ePsBd.js";
|
|
2
|
-
import { c as r, l as i, n as a, o, t as s, u as c } from "./mcp-transport-
|
|
2
|
+
import { c as r, l as i, n as a, o, t as s, u as c } from "./mcp-transport-C6bsIOSV.js";
|
|
3
3
|
//#region src/adapters/mcp-tools.ts
|
|
4
4
|
function l(e) {
|
|
5
5
|
return {
|
|
@@ -23,7 +23,8 @@ function d(a) {
|
|
|
23
23
|
let { semanticLayer: s, getSecurityContext: c, toolPrefix: d = "drizzle_cube_", tools: p = [
|
|
24
24
|
"discover",
|
|
25
25
|
"validate",
|
|
26
|
-
"load"
|
|
26
|
+
"load",
|
|
27
|
+
"chart"
|
|
27
28
|
], prompts: m = r(), resources: h, app: g = !1 } = a, _ = h ?? i(), v = g ? [..._, ...f()] : _, y = o({ appEnabled: g }), b = new Map(y.map((e) => [e.name, e])), x = p.filter((e) => b.has(e)).map((e) => {
|
|
28
29
|
let t = b.get(e), n = {
|
|
29
30
|
name: `${d}${e}`,
|
|
@@ -50,6 +51,10 @@ function d(a) {
|
|
|
50
51
|
let e = i || {};
|
|
51
52
|
return e.query ? l(await n(s, await c(a), e)) : u("query is required");
|
|
52
53
|
}
|
|
54
|
+
case "chart": {
|
|
55
|
+
let e = i || {};
|
|
56
|
+
return e.query ? l(await n(s, await c(a), e)) : u("query is required");
|
|
57
|
+
}
|
|
53
58
|
default: return u(`Unknown tool: ${r}`);
|
|
54
59
|
}
|
|
55
60
|
} catch (e) {
|
|
@@ -26,8 +26,8 @@ function p(e) {
|
|
|
26
26
|
var m = "mcp-session-id";
|
|
27
27
|
function h(e) {
|
|
28
28
|
if (!e) return !1;
|
|
29
|
-
let t = e.split(",").map((e) => e.trim().toLowerCase().split(";")[0]), n = t.some((e) => e === "application/json"), r = t.some((e) => e === "text/event-stream");
|
|
30
|
-
return n && r;
|
|
29
|
+
let t = e.split(",").map((e) => e.trim().toLowerCase().split(";")[0]), n = t.some((e) => e === "application/json"), r = t.some((e) => e === "text/event-stream"), i = t.some((e) => e === "*/*");
|
|
30
|
+
return n && r || n || i;
|
|
31
31
|
}
|
|
32
32
|
function g(e, t = {}) {
|
|
33
33
|
let { allowMissingOrigin: n = !0, allowedOrigins: r } = t;
|
|
@@ -223,19 +223,34 @@ function E(e) {
|
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
name: "load",
|
|
226
|
-
description: "Execute a semantic query and return aggregated results.\n\nSupports regular queries (measures/dimensions), funnel, flow, and retention analysis modes.\nSee the query language prompt for the full DSL reference.\n\nKey rules:\n- Fields are always \"CubeName.fieldName\" format\n- Cross-cube joins: include dimensions from related cubes (system auto-joins)\n- Aggregated totals: use filters with inDateRange (NOT timeDimensions)\n- Time series: use timeDimensions WITH granularity\n- Top N pattern: filters + order + limit\n\
|
|
226
|
+
description: "Execute a semantic query and return aggregated results.\n\nSupports regular queries (measures/dimensions), funnel, flow, and retention analysis modes.\nSee the query language prompt for the full DSL reference.\n\nKey rules:\n- Fields are always \"CubeName.fieldName\" format\n- Cross-cube joins: include dimensions from related cubes (system auto-joins)\n- Aggregated totals: use filters with inDateRange (NOT timeDimensions)\n- Time series: use timeDimensions WITH granularity\n- Top N pattern: filters + order + limit\n\nUse \"load\" for data retrieval. Use \"chart\" to visualise results with an interactive chart.",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
type: "object",
|
|
229
|
+
required: ["query"],
|
|
230
|
+
properties: { query: {
|
|
231
|
+
type: "object",
|
|
232
|
+
description: "Semantic query object. Regular: { measures, dimensions, filters, timeDimensions, order, limit }. Funnel: { funnel: {...} }. Flow: { flow: {...} }. Retention: { retention: {...} }.",
|
|
233
|
+
properties: i
|
|
234
|
+
} }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
];
|
|
238
|
+
if (e?.appEnabled) {
|
|
239
|
+
t.push({
|
|
240
|
+
name: "chart",
|
|
241
|
+
description: "Execute a semantic query and render an interactive chart visualization.\n\nSame query format as \"load\", but renders results in the MCP App chart UI.\nInclude a \"chart\" object to control the visualization.\n\nChart types: bar, line, area, pie, scatter, bubble, radar, treemap, kpiNumber, kpiDelta, table, heatmap, funnel, sankey, sunburst, waterfall, activityGrid, boxPlot\nGuidelines: single number -> kpiNumber, trend -> line/area, categories -> bar, part-of-whole -> pie, correlation -> scatter/bubble, distribution -> boxPlot",
|
|
227
242
|
inputSchema: {
|
|
228
243
|
type: "object",
|
|
229
244
|
required: ["query"],
|
|
230
245
|
properties: {
|
|
231
246
|
query: {
|
|
232
247
|
type: "object",
|
|
233
|
-
description: "Semantic query object.
|
|
248
|
+
description: "Semantic query object. Same format as the load tool.",
|
|
234
249
|
properties: i
|
|
235
250
|
},
|
|
236
251
|
chart: {
|
|
237
252
|
type: "object",
|
|
238
|
-
description: "
|
|
253
|
+
description: "Chart configuration for the visualization. If omitted, chart type is auto-detected from query shape.",
|
|
239
254
|
properties: {
|
|
240
255
|
type: {
|
|
241
256
|
type: "string",
|
|
@@ -336,10 +351,8 @@ function E(e) {
|
|
|
336
351
|
}
|
|
337
352
|
}
|
|
338
353
|
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (e?.appEnabled) {
|
|
342
|
-
let e = t.find((e) => e.name === "load");
|
|
354
|
+
});
|
|
355
|
+
let e = t.find((e) => e.name === "chart");
|
|
343
356
|
e && (e._meta = { ui: { resourceUri: s } });
|
|
344
357
|
}
|
|
345
358
|
return t;
|
|
@@ -361,6 +374,11 @@ async function D(e, i) {
|
|
|
361
374
|
if (!e.query) throw S(-32602, "query is required");
|
|
362
375
|
return O(await r(a, await o(s, c), e));
|
|
363
376
|
}
|
|
377
|
+
case "chart": {
|
|
378
|
+
let e = u || {};
|
|
379
|
+
if (!e.query) throw S(-32602, "query is required");
|
|
380
|
+
return O(await r(a, await o(s, c), e));
|
|
381
|
+
}
|
|
364
382
|
default: throw S(-32601, `Unknown tool: ${l.name}`);
|
|
365
383
|
}
|
|
366
384
|
} catch (e) {
|
|
@@ -405,13 +423,13 @@ var A = a(), j = [{
|
|
|
405
423
|
"Tools:",
|
|
406
424
|
"- discover: { topic?, intent?, limit?, minScore? } -> cubes list",
|
|
407
425
|
"- validate: { query } -> corrected query + issues",
|
|
408
|
-
"- load: { query } -> data + annotation",
|
|
426
|
+
"- load: { query } -> data + annotation (text only)",
|
|
427
|
+
"- chart: { query, chart? } -> data + interactive chart visualization",
|
|
409
428
|
"",
|
|
410
429
|
"Recommended flow:",
|
|
411
|
-
"1) tools/
|
|
412
|
-
"2) tools/call name=
|
|
413
|
-
"3) tools/call name=
|
|
414
|
-
"4) tools/call name=load query=<validated>",
|
|
430
|
+
"1) tools/call name=discover intent=\"<goal>\"",
|
|
431
|
+
"2) tools/call name=validate query=<draft> (optional)",
|
|
432
|
+
"3) tools/call name=load query=<validated> (data only) OR name=chart query=<validated> chart={type, ...} (with visualization)",
|
|
415
433
|
"",
|
|
416
434
|
"Query shapes supported:",
|
|
417
435
|
"- regular: { measures, dimensions, filters, timeDimensions, order, limit, offset, ungrouped }",
|