@arivie/mcp 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/index.d.ts +57 -1
- package/dist/index.js +294 -1
- package/package.json +15 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## 1.0.0
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- Arivie 20 June 2026 release
|
|
6
|
+
|
|
7
|
+
Mastra 1.45 upgrade, Agent Skills spec, and schedule configuration:
|
|
8
|
+
|
|
9
|
+
- Upgrade all `@mastra/*` dependencies to 1.45.0 / 1.21.0 / 1.11.0 / 1.14.0.
|
|
10
|
+
- Migrate every `SKILL.md` file to the official Agent Skills spec format.
|
|
11
|
+
- Add `defineSchedule` / `defineSchedules` configuration and wire schedules to Mastra Workflows with cron triggers.
|
|
12
|
+
- Add `arivie add schedule <name>` CLI command.
|
|
13
|
+
- Forward an optional Mastra `observability` instance through `ArivieConfig` to the Mastra runtime.
|
|
14
|
+
- Remove compatibility glue (`asMastraMcpServer`, `LooseGen`, `applyMaxStepsDefault`) in favor of native Mastra APIs.
|
|
15
|
+
|
|
16
|
+
Tool approval and HITL policy configuration:
|
|
17
|
+
|
|
18
|
+
- Add `ToolApprovalPolicy` type to `ArivieConfig.limits` with support for global, allow-list, deny-list, and function-based policies.
|
|
19
|
+
- Wire tool approval through to Mastra's native `requireToolApproval` / tool-suspension flow.
|
|
20
|
+
- Export `normalizeRequireToolApproval` from `@arivie/agent` for reusable policy normalization.
|
|
21
|
+
|
|
22
|
+
Dogfood eval harness using Mastra `runEvals` and PGlite:
|
|
23
|
+
|
|
24
|
+
- Add `@arivie/core/eval` subpath exporting SQL-semantic scorer helpers and a composite dogfood scorer.
|
|
25
|
+
- Migrate `scripts/run-eval.ts` from the legacy runner to Mastra `runEvals` with a composite scorer.
|
|
26
|
+
- Add PGlite-backed database adapter and adapter selection so `pnpm eval` works without Docker by default.
|
|
27
|
+
- Keep testcontainers path available via `USE_TESTCONTAINERS=1`.
|
|
28
|
+
- Use Mastra `RequestContext` for probe metadata in `runEvals` and register the scorer with Mastra to suppress warnings.
|
|
29
|
+
- Remove the broken `customers` join from the dogfood `orders` semantic entity.
|
|
30
|
+
- Upgrade `@electric-sql/pglite` to 0.5.3 for socket-server compatibility.
|
|
31
|
+
|
|
32
|
+
- Updated dependencies
|
|
33
|
+
- @arivie/agent@0.2.0
|
|
34
|
+
- @arivie/ui-catalog@0.1.1
|
|
35
|
+
- @arivie/db-postgres@0.1.1
|
|
36
|
+
|
|
1
37
|
## 0.0.0
|
|
2
38
|
|
|
3
39
|
- 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
|
-
|
|
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
|
-
|
|
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": "
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
-
"@
|
|
31
|
-
"@
|
|
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.
|
|
34
|
-
"@arivie/semantic": "0.1.0"
|
|
38
|
+
"@arivie/db-postgres": "0.1.1",
|
|
39
|
+
"@arivie/semantic": "0.1.0",
|
|
40
|
+
"@arivie/ui-catalog": "0.1.1"
|
|
35
41
|
},
|
|
36
42
|
"peerDependencies": {
|
|
37
|
-
"@arivie/agent": "0.
|
|
43
|
+
"@arivie/agent": "0.2.0"
|
|
38
44
|
},
|
|
39
45
|
"devDependencies": {
|
|
40
|
-
"@mastra/core": "^1.
|
|
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.
|
|
48
|
-
"@arivie/workspace": "0.1.
|
|
53
|
+
"@arivie/agent": "0.2.0",
|
|
54
|
+
"@arivie/workspace": "0.1.1"
|
|
49
55
|
},
|
|
50
56
|
"publishConfig": {
|
|
51
57
|
"access": "public",
|