@arivie/mcp 0.1.0 → 1.0.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,46 @@
1
+ ## 1.0.1
2
+
3
+ ### Patch Changes
4
+
5
+ - @arivie/agent@0.2.1
6
+ - @arivie/db-postgres@0.1.2
7
+
8
+ ## 1.0.0
9
+
10
+ ### Patch Changes
11
+
12
+ - Arivie 20 June 2026 release
13
+
14
+ Mastra 1.45 upgrade, Agent Skills spec, and schedule configuration:
15
+
16
+ - Upgrade all `@mastra/*` dependencies to 1.45.0 / 1.21.0 / 1.11.0 / 1.14.0.
17
+ - Migrate every `SKILL.md` file to the official Agent Skills spec format.
18
+ - Add `defineSchedule` / `defineSchedules` configuration and wire schedules to Mastra Workflows with cron triggers.
19
+ - Add `arivie add schedule <name>` CLI command.
20
+ - Forward an optional Mastra `observability` instance through `ArivieConfig` to the Mastra runtime.
21
+ - Remove compatibility glue (`asMastraMcpServer`, `LooseGen`, `applyMaxStepsDefault`) in favor of native Mastra APIs.
22
+
23
+ Tool approval and HITL policy configuration:
24
+
25
+ - Add `ToolApprovalPolicy` type to `ArivieConfig.limits` with support for global, allow-list, deny-list, and function-based policies.
26
+ - Wire tool approval through to Mastra's native `requireToolApproval` / tool-suspension flow.
27
+ - Export `normalizeRequireToolApproval` from `@arivie/agent` for reusable policy normalization.
28
+
29
+ Dogfood eval harness using Mastra `runEvals` and PGlite:
30
+
31
+ - Add `@arivie/core/eval` subpath exporting SQL-semantic scorer helpers and a composite dogfood scorer.
32
+ - Migrate `scripts/run-eval.ts` from the legacy runner to Mastra `runEvals` with a composite scorer.
33
+ - Add PGlite-backed database adapter and adapter selection so `pnpm eval` works without Docker by default.
34
+ - Keep testcontainers path available via `USE_TESTCONTAINERS=1`.
35
+ - Use Mastra `RequestContext` for probe metadata in `runEvals` and register the scorer with Mastra to suppress warnings.
36
+ - Remove the broken `customers` join from the dogfood `orders` semantic entity.
37
+ - Upgrade `@electric-sql/pglite` to 0.5.3 for socket-server compatibility.
38
+
39
+ - Updated dependencies
40
+ - @arivie/agent@0.2.0
41
+ - @arivie/ui-catalog@0.1.1
42
+ - @arivie/db-postgres@0.1.1
43
+
1
44
  ## 0.0.0
2
45
 
3
46
  - Initial `makeMcpServer` (Sprint 3 / S3-01): Mastra `MCPServer` with `ask`, `query`, `schema`, `memory` tools and `ask_arivie` agent bridge (REQ-26, RFC §4.7).
package/dist/index.d.ts CHANGED
@@ -2,6 +2,9 @@ import { MCPServer } from '@mastra/mcp';
2
2
  import { Agent } from '@mastra/core/agent';
3
3
  import { PostgresAdapter } from '@arivie/db-postgres';
4
4
  import { SemanticLayer } from '@arivie/semantic';
5
+ import { createMcpApp } from '@json-render/mcp';
6
+ export { registerJsonRenderTool } from '@json-render/mcp';
7
+ export { arivieUiCatalog } from '@arivie/ui-catalog';
5
8
 
6
9
  interface McpOptions {
7
10
  readonly agent: Agent;
@@ -14,4 +17,57 @@ interface McpOptions {
14
17
 
15
18
  declare function makeMcpServer(opts: McpOptions): MCPServer;
16
19
 
17
- export { type McpOptions, makeMcpServer };
20
+ type CreateMcpAppReturn = Awaited<ReturnType<typeof createMcpApp>>;
21
+
22
+ interface MakeMcpUiServerOptions extends McpOptions {
23
+ /**
24
+ * Override the default HTML shell. When omitted, a CDN-loaded shell is
25
+ * served that renders the Arivie catalog. Pass a pre-built bundle here
26
+ * for production deployments (no runtime esm.sh fetch, brand styling).
27
+ */
28
+ html?: string;
29
+ }
30
+ /**
31
+ * Build an MCP server that returns **renderable UI** to MCP UI-aware
32
+ * clients (Claude Desktop, Cursor, ChatGPT). Pairs Arivie's analytics
33
+ * tools (`ask`, `query`, `schema`) with `@json-render/mcp`'s catalog-
34
+ * driven render path.
35
+ *
36
+ * Wire shape:
37
+ * 1. `createMcpApp({ catalog, html })` registers the canonical
38
+ * `render-ui` tool + the UI resource at `ui://render-ui/view.html`.
39
+ * MCP clients use this when the agent emits a json-render spec.
40
+ * 2. We layer Arivie's `ask` / `query` / `schema` tools on top of the
41
+ * same server. Their results are JSON-shaped (rows + SQL + metadata)
42
+ * so the client can either render them as plain JSON OR pass them
43
+ * back through `render-ui` with a spec.
44
+ *
45
+ * Catalog: 36 shadcn components + 4 Arivie-specific
46
+ * (`ArivieMetric`, `ArivieQueryResult`, `ArivieVerdict`,
47
+ * `ArivieSemanticEntity`).
48
+ *
49
+ * Smaller models (Gemini Flash, Grok) sometimes emit malformed specs;
50
+ * the json-render tool validates against the catalog's Zod schema and
51
+ * returns the partial spec rather than crashing.
52
+ */
53
+ declare function makeMcpUiServer(opts: MakeMcpUiServerOptions): Promise<CreateMcpAppReturn>;
54
+
55
+ /**
56
+ * Minimal self-contained HTML shell served as the json-render UI
57
+ * resource (`ui://render-ui/view.html`). MCP UI clients (Claude Desktop,
58
+ * Cursor, ChatGPT) open this in an iframe; the parent posts the tool
59
+ * result containing a json-render spec via `ontoolresult`, and this
60
+ * shell renders the spec.
61
+ *
62
+ * Implementation note: we load React + json-render packages from esm.sh
63
+ * at runtime instead of bundling them. This keeps `@arivie/mcp` from
64
+ * shipping a 200KB+ HTML blob and means the shell auto-tracks
65
+ * @json-render/* versions we install. The tradeoff is the shell does a
66
+ * network fetch on first render; for a dev/spike server this is fine.
67
+ *
68
+ * Customise this template (or replace via `makeMcpUiServer({ html })`)
69
+ * to ship a hand-tuned production shell with bundled assets + brand styling.
70
+ */
71
+ declare const DEFAULT_UI_SHELL_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Arivie</title>\n <style>\n :root {\n --bg: #0b1115;\n --fg: #e7eef3;\n --muted: #8aa0ad;\n --accent: #0d9488;\n --warn: #fb923c;\n }\n @media (prefers-color-scheme: light) {\n :root {\n --bg: #ffffff;\n --fg: #0b1115;\n --muted: #5b6770;\n --accent: #0d9488;\n --warn: #c2570a;\n }\n }\n html, body, #root { height: 100%; margin: 0; }\n body {\n background: var(--bg);\n color: var(--fg);\n font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n Roboto, \"Helvetica Neue\", Arial, sans-serif;\n line-height: 1.5;\n }\n .empty { padding: 24px; color: var(--muted); font-size: 14px; }\n .err { padding: 16px; color: var(--warn); font-family: ui-monospace, monospace;\n font-size: 12px; white-space: pre-wrap; }\n </style>\n </head>\n <body>\n <div id=\"root\">\n <div class=\"empty\">Waiting for spec from the Arivie agent\u2026</div>\n </div>\n <script type=\"module\">\n // Pulled at runtime \u2014 these versions match what @arivie/mcp depends on.\n import * as React from \"https://esm.sh/react@19.1?bundle\";\n import * as ReactDOM from \"https://esm.sh/react-dom@19.1/client?bundle\";\n import { defineRegistry, Renderer } from \"https://esm.sh/@json-render/react@0.19?bundle\";\n import { shadcnComponentDefinitions } from \"https://esm.sh/@json-render/shadcn@0.19/catalog?bundle\";\n import { defineCatalog } from \"https://esm.sh/@json-render/core@0.19?bundle\";\n import { schema } from \"https://esm.sh/@json-render/react@0.19/schema?bundle\";\n\n // A bare-minimum mirror of the server-side Arivie catalog. We can't\n // import the server catalog directly (this runs in the browser), so\n // we re-declare the components the renderer needs. The Zod schema is\n // only used for VALIDATION on the server; the renderer just looks up\n // component names \u2192 React components in the registry.\n const catalog = defineCatalog(schema, {\n components: {\n ...shadcnComponentDefinitions,\n ArivieMetric: { props: schema.object({}), description: \"\" },\n ArivieQueryResult: { props: schema.object({}), description: \"\" },\n ArivieVerdict: { props: schema.object({}), description: \"\" },\n ArivieSemanticEntity: { props: schema.object({}), description: \"\" },\n },\n actions: {},\n });\n\n const fmt = (v, format) => {\n if (v == null) return \"\u2014\";\n if (format === \"currency\") {\n const n = Number(v);\n if (!Number.isFinite(n)) return String(v);\n return n.toLocaleString(undefined, { style: \"currency\", currency: \"USD\" });\n }\n if (format === \"percent\") return `${v}%`;\n return String(v);\n };\n\n const { registry } = defineRegistry(catalog, {\n components: {\n ArivieMetric: ({ props }) =>\n React.createElement(\"div\", {\n style: {\n padding: \"16px\", border: \"1px solid #2a3942\", borderRadius: 12,\n display: \"inline-block\", minWidth: 220, margin: 8,\n },\n }, [\n React.createElement(\"div\", { key: \"l\", style: { color: \"var(--muted)\", fontSize: 13 } }, props.label),\n React.createElement(\"div\", { key: \"v\", style: { fontSize: 28, fontWeight: 600, marginTop: 4 } },\n fmt(props.value, props.format)),\n props.delta ? React.createElement(\"div\", { key: \"d\",\n style: { fontSize: 13, marginTop: 4,\n color: props.deltaDirection === \"down\" ? \"var(--warn)\" : \"var(--accent)\" }\n }, `${props.deltaDirection === \"down\" ? \"\u25BC\" : \"\u25B2\"} ${props.delta}`) : null,\n ]),\n ArivieQueryResult: ({ props }) =>\n React.createElement(\"div\", { style: { margin: 12 } }, [\n React.createElement(\"pre\", {\n key: \"sql\",\n style: {\n background: \"#0e1820\", color: \"#cde8ff\", padding: 12, borderRadius: 8,\n fontSize: 12, overflow: \"auto\", maxHeight: 240,\n },\n }, props.sql),\n React.createElement(\"table\", {\n key: \"t\",\n style: { width: \"100%\", borderCollapse: \"collapse\", marginTop: 8, fontSize: 13 },\n }, [\n React.createElement(\"thead\", { key: \"h\" },\n React.createElement(\"tr\", null, Object.keys(props.rows?.[0] ?? {}).map(k =>\n React.createElement(\"th\", {\n key: k,\n style: { textAlign: \"left\", padding: \"6px 10px\", borderBottom: \"1px solid #2a3942\" },\n }, k)))),\n React.createElement(\"tbody\", { key: \"b\" },\n (props.rows ?? []).map((row, i) =>\n React.createElement(\"tr\", { key: i },\n Object.values(row).map((v, j) =>\n React.createElement(\"td\", {\n key: j,\n style: { padding: \"6px 10px\", borderBottom: \"1px solid #1a242c\" },\n }, String(v)))))),\n ]),\n React.createElement(\"div\", {\n key: \"meta\",\n style: { fontSize: 12, color: \"var(--muted)\", marginTop: 6 },\n }, `${props.rows?.length ?? 0} row(s)${props.durationMs ? ` \u00B7 ${props.durationMs}ms` : \"\"}${props.truncated ? \" \u00B7 truncated\" : \"\"}`),\n ]),\n ArivieVerdict: ({ props }) => {\n const colors = {\n healthy: { bg: \"#0e2a22\", fg: \"#10b981\" },\n watch: { bg: \"#2a230e\", fg: \"#fbbf24\" },\n breached:{ bg: \"#2a1010\", fg: \"#f87171\" },\n info: { bg: \"#0e1c2a\", fg: \"#60a5fa\" },\n };\n const c = colors[props.status] ?? colors.info;\n return React.createElement(\"div\", {\n style: {\n display: \"inline-flex\", alignItems: \"center\", gap: 8, padding: \"6px 14px\",\n borderRadius: 999, background: c.bg, color: c.fg, fontSize: 13, margin: 8,\n },\n }, [\n React.createElement(\"strong\", { key: \"s\" }, props.status.toUpperCase()),\n React.createElement(\"span\", { key: \"m\" }, props.message),\n props.threshold ? React.createElement(\"span\", { key: \"t\", style: { opacity: 0.7 } }, `(${props.threshold})`) : null,\n ]);\n },\n ArivieSemanticEntity: ({ props }) =>\n React.createElement(\"div\", {\n style: {\n margin: 12, padding: 16, border: \"1px solid #2a3942\", borderRadius: 12,\n },\n }, [\n React.createElement(\"h3\", { key: \"n\", style: { margin: 0, fontSize: 16 } }, props.entityName),\n props.description ? React.createElement(\"p\", { key: \"d\",\n style: { color: \"var(--muted)\", fontSize: 13 } }, props.description) : null,\n [\"measures\", \"dimensions\", \"segments\"].map(kind =>\n (props[kind]?.length ?? 0) > 0\n ? React.createElement(\"div\", { key: kind, style: { marginTop: 8 } }, [\n React.createElement(\"div\", { key: \"h\",\n style: { fontSize: 12, color: \"var(--muted)\", textTransform: \"uppercase\" } }, kind),\n React.createElement(\"ul\", { key: \"u\", style: { margin: \"4px 0 0\", paddingLeft: 18 } },\n props[kind].map((x, i) => React.createElement(\"li\", { key: i, style: { fontSize: 13 } },\n [React.createElement(\"strong\", { key: \"n\" }, x.name),\n x.description ? ` \u2014 ${x.description}` : \"\"]))),\n ])\n : null\n ),\n ]),\n },\n });\n\n const root = ReactDOM.createRoot(document.getElementById(\"root\"));\n\n function render(spec) {\n try {\n root.render(React.createElement(Renderer, { spec, registry }));\n } catch (err) {\n root.render(React.createElement(\"div\", { className: \"err\" },\n `Render error: ${err?.message ?? String(err)}`));\n }\n }\n\n // MCP UI clients post the tool result via window.ontoolresult.\n window.ontoolresult = (result) => {\n const text = result?.content?.[0]?.text;\n if (!text) return;\n try {\n const spec = JSON.parse(text);\n render(spec);\n } catch (err) {\n root.render(React.createElement(\"div\", { className: \"err\" },\n `Bad spec JSON: ${err?.message ?? String(err)}`));\n }\n };\n </script>\n </body>\n</html>\n";
72
+
73
+ export { DEFAULT_UI_SHELL_HTML, type MakeMcpUiServerOptions, type McpOptions, makeMcpServer, makeMcpUiServer };
package/dist/index.js CHANGED
@@ -2,6 +2,10 @@ import { validateExecuteSql } from '@arivie/db-postgres';
2
2
  import { createTool } from '@mastra/core/tools';
3
3
  import { MCPServer } from '@mastra/mcp';
4
4
  import { z } from 'zod';
5
+ import { createMcpApp } from '@json-render/mcp';
6
+ export { registerJsonRenderTool } from '@json-render/mcp';
7
+ import { arivieUiCatalog } from '@arivie/ui-catalog';
8
+ export { arivieUiCatalog } from '@arivie/ui-catalog';
5
9
 
6
10
  // src/make-server.ts
7
11
  var ASK_DESCRIPTION = "Ask Arivie a question; runs the agent's full conversational round-trip.";
@@ -100,4 +104,293 @@ function makeMcpServer(opts) {
100
104
  });
101
105
  }
102
106
 
103
- export { makeMcpServer };
107
+ // src/ui-shell.ts
108
+ var DEFAULT_UI_SHELL_HTML = `<!doctype html>
109
+ <html lang="en">
110
+ <head>
111
+ <meta charset="utf-8" />
112
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
113
+ <title>Arivie</title>
114
+ <style>
115
+ :root {
116
+ --bg: #0b1115;
117
+ --fg: #e7eef3;
118
+ --muted: #8aa0ad;
119
+ --accent: #0d9488;
120
+ --warn: #fb923c;
121
+ }
122
+ @media (prefers-color-scheme: light) {
123
+ :root {
124
+ --bg: #ffffff;
125
+ --fg: #0b1115;
126
+ --muted: #5b6770;
127
+ --accent: #0d9488;
128
+ --warn: #c2570a;
129
+ }
130
+ }
131
+ html, body, #root { height: 100%; margin: 0; }
132
+ body {
133
+ background: var(--bg);
134
+ color: var(--fg);
135
+ font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
136
+ Roboto, "Helvetica Neue", Arial, sans-serif;
137
+ line-height: 1.5;
138
+ }
139
+ .empty { padding: 24px; color: var(--muted); font-size: 14px; }
140
+ .err { padding: 16px; color: var(--warn); font-family: ui-monospace, monospace;
141
+ font-size: 12px; white-space: pre-wrap; }
142
+ </style>
143
+ </head>
144
+ <body>
145
+ <div id="root">
146
+ <div class="empty">Waiting for spec from the Arivie agent\u2026</div>
147
+ </div>
148
+ <script type="module">
149
+ // Pulled at runtime \u2014 these versions match what @arivie/mcp depends on.
150
+ import * as React from "https://esm.sh/react@19.1?bundle";
151
+ import * as ReactDOM from "https://esm.sh/react-dom@19.1/client?bundle";
152
+ import { defineRegistry, Renderer } from "https://esm.sh/@json-render/react@0.19?bundle";
153
+ import { shadcnComponentDefinitions } from "https://esm.sh/@json-render/shadcn@0.19/catalog?bundle";
154
+ import { defineCatalog } from "https://esm.sh/@json-render/core@0.19?bundle";
155
+ import { schema } from "https://esm.sh/@json-render/react@0.19/schema?bundle";
156
+
157
+ // A bare-minimum mirror of the server-side Arivie catalog. We can't
158
+ // import the server catalog directly (this runs in the browser), so
159
+ // we re-declare the components the renderer needs. The Zod schema is
160
+ // only used for VALIDATION on the server; the renderer just looks up
161
+ // component names \u2192 React components in the registry.
162
+ const catalog = defineCatalog(schema, {
163
+ components: {
164
+ ...shadcnComponentDefinitions,
165
+ ArivieMetric: { props: schema.object({}), description: "" },
166
+ ArivieQueryResult: { props: schema.object({}), description: "" },
167
+ ArivieVerdict: { props: schema.object({}), description: "" },
168
+ ArivieSemanticEntity: { props: schema.object({}), description: "" },
169
+ },
170
+ actions: {},
171
+ });
172
+
173
+ const fmt = (v, format) => {
174
+ if (v == null) return "\u2014";
175
+ if (format === "currency") {
176
+ const n = Number(v);
177
+ if (!Number.isFinite(n)) return String(v);
178
+ return n.toLocaleString(undefined, { style: "currency", currency: "USD" });
179
+ }
180
+ if (format === "percent") return \`\${v}%\`;
181
+ return String(v);
182
+ };
183
+
184
+ const { registry } = defineRegistry(catalog, {
185
+ components: {
186
+ ArivieMetric: ({ props }) =>
187
+ React.createElement("div", {
188
+ style: {
189
+ padding: "16px", border: "1px solid #2a3942", borderRadius: 12,
190
+ display: "inline-block", minWidth: 220, margin: 8,
191
+ },
192
+ }, [
193
+ React.createElement("div", { key: "l", style: { color: "var(--muted)", fontSize: 13 } }, props.label),
194
+ React.createElement("div", { key: "v", style: { fontSize: 28, fontWeight: 600, marginTop: 4 } },
195
+ fmt(props.value, props.format)),
196
+ props.delta ? React.createElement("div", { key: "d",
197
+ style: { fontSize: 13, marginTop: 4,
198
+ color: props.deltaDirection === "down" ? "var(--warn)" : "var(--accent)" }
199
+ }, \`\${props.deltaDirection === "down" ? "\u25BC" : "\u25B2"} \${props.delta}\`) : null,
200
+ ]),
201
+ ArivieQueryResult: ({ props }) =>
202
+ React.createElement("div", { style: { margin: 12 } }, [
203
+ React.createElement("pre", {
204
+ key: "sql",
205
+ style: {
206
+ background: "#0e1820", color: "#cde8ff", padding: 12, borderRadius: 8,
207
+ fontSize: 12, overflow: "auto", maxHeight: 240,
208
+ },
209
+ }, props.sql),
210
+ React.createElement("table", {
211
+ key: "t",
212
+ style: { width: "100%", borderCollapse: "collapse", marginTop: 8, fontSize: 13 },
213
+ }, [
214
+ React.createElement("thead", { key: "h" },
215
+ React.createElement("tr", null, Object.keys(props.rows?.[0] ?? {}).map(k =>
216
+ React.createElement("th", {
217
+ key: k,
218
+ style: { textAlign: "left", padding: "6px 10px", borderBottom: "1px solid #2a3942" },
219
+ }, k)))),
220
+ React.createElement("tbody", { key: "b" },
221
+ (props.rows ?? []).map((row, i) =>
222
+ React.createElement("tr", { key: i },
223
+ Object.values(row).map((v, j) =>
224
+ React.createElement("td", {
225
+ key: j,
226
+ style: { padding: "6px 10px", borderBottom: "1px solid #1a242c" },
227
+ }, String(v)))))),
228
+ ]),
229
+ React.createElement("div", {
230
+ key: "meta",
231
+ style: { fontSize: 12, color: "var(--muted)", marginTop: 6 },
232
+ }, \`\${props.rows?.length ?? 0} row(s)\${props.durationMs ? \` \xB7 \${props.durationMs}ms\` : ""}\${props.truncated ? " \xB7 truncated" : ""}\`),
233
+ ]),
234
+ ArivieVerdict: ({ props }) => {
235
+ const colors = {
236
+ healthy: { bg: "#0e2a22", fg: "#10b981" },
237
+ watch: { bg: "#2a230e", fg: "#fbbf24" },
238
+ breached:{ bg: "#2a1010", fg: "#f87171" },
239
+ info: { bg: "#0e1c2a", fg: "#60a5fa" },
240
+ };
241
+ const c = colors[props.status] ?? colors.info;
242
+ return React.createElement("div", {
243
+ style: {
244
+ display: "inline-flex", alignItems: "center", gap: 8, padding: "6px 14px",
245
+ borderRadius: 999, background: c.bg, color: c.fg, fontSize: 13, margin: 8,
246
+ },
247
+ }, [
248
+ React.createElement("strong", { key: "s" }, props.status.toUpperCase()),
249
+ React.createElement("span", { key: "m" }, props.message),
250
+ props.threshold ? React.createElement("span", { key: "t", style: { opacity: 0.7 } }, \`(\${props.threshold})\`) : null,
251
+ ]);
252
+ },
253
+ ArivieSemanticEntity: ({ props }) =>
254
+ React.createElement("div", {
255
+ style: {
256
+ margin: 12, padding: 16, border: "1px solid #2a3942", borderRadius: 12,
257
+ },
258
+ }, [
259
+ React.createElement("h3", { key: "n", style: { margin: 0, fontSize: 16 } }, props.entityName),
260
+ props.description ? React.createElement("p", { key: "d",
261
+ style: { color: "var(--muted)", fontSize: 13 } }, props.description) : null,
262
+ ["measures", "dimensions", "segments"].map(kind =>
263
+ (props[kind]?.length ?? 0) > 0
264
+ ? React.createElement("div", { key: kind, style: { marginTop: 8 } }, [
265
+ React.createElement("div", { key: "h",
266
+ style: { fontSize: 12, color: "var(--muted)", textTransform: "uppercase" } }, kind),
267
+ React.createElement("ul", { key: "u", style: { margin: "4px 0 0", paddingLeft: 18 } },
268
+ props[kind].map((x, i) => React.createElement("li", { key: i, style: { fontSize: 13 } },
269
+ [React.createElement("strong", { key: "n" }, x.name),
270
+ x.description ? \` \u2014 \${x.description}\` : ""]))),
271
+ ])
272
+ : null
273
+ ),
274
+ ]),
275
+ },
276
+ });
277
+
278
+ const root = ReactDOM.createRoot(document.getElementById("root"));
279
+
280
+ function render(spec) {
281
+ try {
282
+ root.render(React.createElement(Renderer, { spec, registry }));
283
+ } catch (err) {
284
+ root.render(React.createElement("div", { className: "err" },
285
+ \`Render error: \${err?.message ?? String(err)}\`));
286
+ }
287
+ }
288
+
289
+ // MCP UI clients post the tool result via window.ontoolresult.
290
+ window.ontoolresult = (result) => {
291
+ const text = result?.content?.[0]?.text;
292
+ if (!text) return;
293
+ try {
294
+ const spec = JSON.parse(text);
295
+ render(spec);
296
+ } catch (err) {
297
+ root.render(React.createElement("div", { className: "err" },
298
+ \`Bad spec JSON: \${err?.message ?? String(err)}\`));
299
+ }
300
+ };
301
+ </script>
302
+ </body>
303
+ </html>
304
+ `;
305
+
306
+ // src/make-ui-server.ts
307
+ async function makeMcpUiServer(opts) {
308
+ const html = opts.html ?? DEFAULT_UI_SHELL_HTML;
309
+ const server = await createMcpApp({
310
+ name: `Arivie (${opts.ownerName})`,
311
+ version: "0.3.0",
312
+ catalog: arivieUiCatalog,
313
+ html,
314
+ tool: {
315
+ name: "render_arivie_ui",
316
+ title: "Render Arivie UI",
317
+ description: "Render an interactive UI for an analytics result. Pass a json-render `spec` built from the Arivie catalog (ArivieMetric, ArivieQueryResult, ArivieVerdict, ArivieSemanticEntity, plus shadcn components). The MCP client renders the spec inside its UI surface."
318
+ }
319
+ });
320
+ const s = server;
321
+ s.registerTool(
322
+ "ask",
323
+ {
324
+ title: "Ask Arivie",
325
+ description: "Ask Arivie a natural-language question. Runs the full agent loop (semantic-layer-grounded SQL + optional file artifacts). Returns the agent's answer text plus the tool trace. For UI rendering, also consider calling `render_arivie_ui` with a spec built from the result.",
326
+ inputSchema: {
327
+ prompt: z.string().describe("The question to ask Arivie")
328
+ }
329
+ },
330
+ async ({ prompt }) => {
331
+ const result = await opts.agent.generate(prompt);
332
+ const text = result != null && typeof result === "object" && "text" in result ? String(result.text) : String(result);
333
+ return { content: [{ type: "text", text }] };
334
+ }
335
+ );
336
+ s.registerTool(
337
+ "query",
338
+ {
339
+ title: "Execute SQL",
340
+ description: "Execute a read-only SQL query against the owner's database. Returns rows + SQL + timing. To render as a table card, pass the result into `render_arivie_ui` with an ArivieQueryResult spec.",
341
+ inputSchema: {
342
+ sql: z.string().describe("A SELECT or WITH SQL query")
343
+ }
344
+ },
345
+ async ({ sql }) => {
346
+ const trimmed = sql.trim();
347
+ validateExecuteSql(trimmed);
348
+ const start = Date.now();
349
+ const result = await opts.db.execute({
350
+ query: trimmed,
351
+ runAsRole: "arivie_reader",
352
+ userId: "mcp",
353
+ rowLimit: 50,
354
+ timeoutMs: 3e4
355
+ });
356
+ const durationMs = Date.now() - start;
357
+ return {
358
+ content: [
359
+ {
360
+ type: "text",
361
+ text: JSON.stringify({
362
+ rows: result.rows,
363
+ rowCount: result.rowCount,
364
+ truncated: result.truncated,
365
+ durationMs,
366
+ sql: trimmed,
367
+ source: "postgres"
368
+ })
369
+ }
370
+ ]
371
+ };
372
+ }
373
+ );
374
+ s.registerTool(
375
+ "schema",
376
+ {
377
+ title: "Semantic Layer",
378
+ description: "Return the semantic-layer catalog + entities for this owner. Each entity surfaces measures, dimensions, segments, joins. To render one entity as a card, pass it into `render_arivie_ui` with an ArivieSemanticEntity spec.",
379
+ inputSchema: {}
380
+ },
381
+ async () => ({
382
+ content: [
383
+ {
384
+ type: "text",
385
+ text: JSON.stringify({
386
+ catalog: opts.semantic.catalog,
387
+ entities: [...opts.semantic.entities.values()]
388
+ })
389
+ }
390
+ ]
391
+ })
392
+ );
393
+ return server;
394
+ }
395
+
396
+ export { DEFAULT_UI_SHELL_HTML, makeMcpServer, makeMcpUiServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arivie/mcp",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Arivie Mastra MCP server — ask, query, schema, and memory tools over MCPServer.",
@@ -27,25 +27,31 @@
27
27
  "CHANGELOG.md"
28
28
  ],
29
29
  "dependencies": {
30
- "@mastra/core": "^1.35.0",
31
- "@mastra/mcp": "^1.7.0",
30
+ "@json-render/core": "^0.19.0",
31
+ "@json-render/mcp": "^0.19.0",
32
+ "@json-render/react": "^0.19.0",
33
+ "@json-render/shadcn": "^0.19.0",
34
+ "@mastra/core": "^1.45.0",
35
+ "@mastra/mcp": "^1.11.0",
36
+ "@modelcontextprotocol/sdk": "^1.29.0",
32
37
  "zod": "^4.4.0",
33
- "@arivie/db-postgres": "0.1.0",
34
- "@arivie/semantic": "0.1.0"
38
+ "@arivie/semantic": "0.1.0",
39
+ "@arivie/ui-catalog": "0.1.1",
40
+ "@arivie/db-postgres": "0.1.2"
35
41
  },
36
42
  "peerDependencies": {
37
- "@arivie/agent": "0.1.0"
43
+ "@arivie/agent": "0.2.1"
38
44
  },
39
45
  "devDependencies": {
40
- "@mastra/core": "^1.35.0",
46
+ "@mastra/core": "^1.45.0",
41
47
  "@types/node": "^22.0.0",
42
48
  "ai": "^6.0.0",
43
49
  "tsup": "^8.5.1",
44
50
  "typescript": "^6.0.0",
45
51
  "vitest": "^4.1.0",
46
52
  "zod": "^4.4.0",
47
- "@arivie/agent": "0.1.0",
48
- "@arivie/workspace": "0.1.0"
53
+ "@arivie/agent": "0.2.1",
54
+ "@arivie/workspace": "0.1.1"
49
55
  },
50
56
  "publishConfig": {
51
57
  "access": "public",