@mongrov/analytics 0.1.0-alpha.8 → 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +47 -11
  2. package/dist/core/factory.d.ts.map +1 -1
  3. package/dist/core/factory.js +26 -0
  4. package/dist/core/factory.js.map +1 -1
  5. package/dist/core/schemas.js +18 -18
  6. package/dist/core/warehouse.d.ts.map +1 -1
  7. package/dist/core/warehouse.js +13 -1
  8. package/dist/core/warehouse.js.map +1 -1
  9. package/dist/tools/__fakes__/engine.d.ts +25 -0
  10. package/dist/tools/__fakes__/engine.d.ts.map +1 -0
  11. package/dist/tools/__fakes__/engine.js +37 -0
  12. package/dist/tools/__fakes__/engine.js.map +1 -0
  13. package/dist/tools/audit.d.ts +31 -0
  14. package/dist/tools/audit.d.ts.map +1 -0
  15. package/dist/tools/audit.js +157 -0
  16. package/dist/tools/audit.js.map +1 -0
  17. package/dist/tools/authorize.d.ts +36 -0
  18. package/dist/tools/authorize.d.ts.map +1 -0
  19. package/dist/tools/authorize.js +87 -0
  20. package/dist/tools/authorize.js.map +1 -0
  21. package/dist/tools/budget.d.ts +15 -0
  22. package/dist/tools/budget.d.ts.map +1 -0
  23. package/dist/tools/budget.js +64 -0
  24. package/dist/tools/budget.js.map +1 -0
  25. package/dist/tools/factory.d.ts +43 -0
  26. package/dist/tools/factory.d.ts.map +1 -0
  27. package/dist/tools/factory.js +137 -0
  28. package/dist/tools/factory.js.map +1 -0
  29. package/dist/tools/formatters.d.ts +17 -0
  30. package/dist/tools/formatters.d.ts.map +1 -0
  31. package/dist/tools/formatters.js +30 -0
  32. package/dist/tools/formatters.js.map +1 -0
  33. package/dist/tools/impls/activity.d.ts +15 -0
  34. package/dist/tools/impls/activity.d.ts.map +1 -0
  35. package/dist/tools/impls/activity.js +61 -0
  36. package/dist/tools/impls/activity.js.map +1 -0
  37. package/dist/tools/impls/anomaly.d.ts +21 -0
  38. package/dist/tools/impls/anomaly.d.ts.map +1 -0
  39. package/dist/tools/impls/anomaly.js +70 -0
  40. package/dist/tools/impls/anomaly.js.map +1 -0
  41. package/dist/tools/impls/compare.d.ts +21 -0
  42. package/dist/tools/impls/compare.d.ts.map +1 -0
  43. package/dist/tools/impls/compare.js +68 -0
  44. package/dist/tools/impls/compare.js.map +1 -0
  45. package/dist/tools/impls/hrv.d.ts +15 -0
  46. package/dist/tools/impls/hrv.d.ts.map +1 -0
  47. package/dist/tools/impls/hrv.js +36 -0
  48. package/dist/tools/impls/hrv.js.map +1 -0
  49. package/dist/tools/impls/insights.d.ts +18 -0
  50. package/dist/tools/impls/insights.d.ts.map +1 -0
  51. package/dist/tools/impls/insights.js +41 -0
  52. package/dist/tools/impls/insights.js.map +1 -0
  53. package/dist/tools/impls/sleep.d.ts +15 -0
  54. package/dist/tools/impls/sleep.d.ts.map +1 -0
  55. package/dist/tools/impls/sleep.js +43 -0
  56. package/dist/tools/impls/sleep.js.map +1 -0
  57. package/dist/tools/index.d.ts +13 -1
  58. package/dist/tools/index.d.ts.map +1 -1
  59. package/dist/tools/index.js +21 -1
  60. package/dist/tools/index.js.map +1 -1
  61. package/dist/tools/mcp/adapter.d.ts +33 -0
  62. package/dist/tools/mcp/adapter.d.ts.map +1 -0
  63. package/dist/tools/mcp/adapter.js +56 -0
  64. package/dist/tools/mcp/adapter.js.map +1 -0
  65. package/dist/tools/mcp/guard.d.ts +17 -0
  66. package/dist/tools/mcp/guard.d.ts.map +1 -0
  67. package/dist/tools/mcp/guard.js +22 -0
  68. package/dist/tools/mcp/guard.js.map +1 -0
  69. package/dist/tools/mcp/index.d.ts +20 -0
  70. package/dist/tools/mcp/index.d.ts.map +1 -0
  71. package/dist/tools/mcp/index.js +16 -0
  72. package/dist/tools/mcp/index.js.map +1 -0
  73. package/dist/tools/mcp/server.d.ts +39 -0
  74. package/dist/tools/mcp/server.d.ts.map +1 -0
  75. package/dist/tools/mcp/server.js +70 -0
  76. package/dist/tools/mcp/server.js.map +1 -0
  77. package/dist/tools/mcp/transports/http.d.ts +54 -0
  78. package/dist/tools/mcp/transports/http.d.ts.map +1 -0
  79. package/dist/tools/mcp/transports/http.js +133 -0
  80. package/dist/tools/mcp/transports/http.js.map +1 -0
  81. package/dist/tools/mcp/transports/stdio.d.ts +22 -0
  82. package/dist/tools/mcp/transports/stdio.d.ts.map +1 -0
  83. package/dist/tools/mcp/transports/stdio.js +19 -0
  84. package/dist/tools/mcp/transports/stdio.js.map +1 -0
  85. package/dist/tools/rate-limit.d.ts +26 -0
  86. package/dist/tools/rate-limit.d.ts.map +1 -0
  87. package/dist/tools/rate-limit.js +67 -0
  88. package/dist/tools/rate-limit.js.map +1 -0
  89. package/dist/tools/types.d.ts +121 -0
  90. package/dist/tools/types.d.ts.map +1 -0
  91. package/dist/tools/types.js +16 -0
  92. package/dist/tools/types.js.map +1 -0
  93. package/dist/tools/wrap.d.ts +53 -0
  94. package/dist/tools/wrap.d.ts.map +1 -0
  95. package/dist/tools/wrap.js +135 -0
  96. package/dist/tools/wrap.js.map +1 -0
  97. package/package.json +21 -4
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `createMcpServer` — wrap an `AnalyticsToolsHandle` in an MCP server
3
+ * that speaks `tools/list` + `tools/call` to any MCP client.
4
+ *
5
+ * We use the low-level `Server` + `setRequestHandler` rather than the
6
+ * higher-level `McpServer.registerTool`, because `registerTool` wants
7
+ * either a Zod raw shape or an `AnySchema`, and our tools were built
8
+ * around `zod.object(...)` (not raw shapes) and are already converted
9
+ * to JSON Schema by `toMcpTools`. Emitting the pre-baked JSON Schema
10
+ * from `tools/list` keeps the source of truth in one place (the Zod
11
+ * schemas in `src/tools/impls/*.ts`).
12
+ *
13
+ * `call_tool` re-dispatches into the wrapped `tool.execute`, which
14
+ * runs the full rate → auth → execute → budget → audit chain via the
15
+ * ctx container the handle owns. `handle.setContext(...)` before an
16
+ * MCP session behaves identically to setting it before an AI SDK turn.
17
+ */
18
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
19
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
20
+ import { toMcpTools } from './adapter';
21
+ export function createMcpServer(config) {
22
+ const mcpTools = toMcpTools(config.toolsHandle);
23
+ const byName = new Map(mcpTools.map(t => [t.name, t]));
24
+ const server = new Server({
25
+ name: config.name ?? 'mongrov-analytics',
26
+ version: config.version ?? '0.1.0',
27
+ }, { capabilities: { tools: {} } });
28
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29
+ tools: mcpTools.map(t => ({
30
+ name: t.name,
31
+ description: t.description,
32
+ inputSchema: t.inputSchema,
33
+ })),
34
+ }));
35
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
36
+ const tool = byName.get(req.params.name);
37
+ if (!tool) {
38
+ return {
39
+ content: [{
40
+ type: 'text',
41
+ text: `Unknown tool: ${req.params.name}`,
42
+ }],
43
+ isError: true,
44
+ };
45
+ }
46
+ try {
47
+ return await tool.handler(req.params.arguments ?? {});
48
+ }
49
+ catch (err) {
50
+ const message = err instanceof Error ? err.message : String(err);
51
+ config.logger?.error('mcp tool handler threw', {
52
+ toolName: req.params.name,
53
+ err: message,
54
+ });
55
+ return {
56
+ content: [{
57
+ type: 'text',
58
+ text: `Tool call failed.`,
59
+ }],
60
+ isError: true,
61
+ };
62
+ }
63
+ });
64
+ return {
65
+ server,
66
+ connect: transport => server.connect(transport),
67
+ close: () => server.close(),
68
+ };
69
+ }
70
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/tools/mcp/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAElE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAA;AAG3C,OAAO,EAAgB,UAAU,EAAE,MAAM,WAAW,CAAA;AAoBpD,MAAM,UAAU,eAAe,CAC7B,MAA6B;IAE7B,MAAM,QAAQ,GAAc,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAU,CAAC,CAAC,CAAA;IAE/D,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB;QACE,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,mBAAmB;QACxC,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,OAAO;KACnC,EACD,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAA;IAED,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACxB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAId;SACF,CAAC,CAAC;KACJ,CAAC,CAAC,CAAA;IAEH,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACxC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,iBAAiB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;qBACzC,CAAC;gBACF,OAAO,EAAE,IAAI;aACd,CAAA;QACH,CAAC;QACD,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;QACvD,CAAC;QACD,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,wBAAwB,EAAE;gBAC7C,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;gBACzB,GAAG,EAAE,OAAO;aACb,CAAC,CAAA;YACF,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,mBAAmB;qBAC1B,CAAC;gBACF,OAAO,EAAE,IAAI;aACd,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO;QACL,MAAM;QACN,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;QAC/C,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE;KAC5B,CAAA;AACH,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `createHttpTransport` — Node HTTP transport for `createMcpServer`.
3
+ *
4
+ * Boots a bare `node:http` server that gates requests behind an
5
+ * optional bearer token, then hands them to the SDK's
6
+ * `StreamableHTTPServerTransport`. Runs in stateless mode
7
+ * (no `sessionIdGenerator`) — every request is self-contained, which
8
+ * matches how our tools are already wired (`handle.setContext(...)`
9
+ * carries the per-user scope, not the transport).
10
+ *
11
+ * Deliberately minimal:
12
+ * - one path (default `/mcp`)
13
+ * - one method (POST + GET both dispatched to the SDK)
14
+ * - bearer via `Authorization: Bearer <token>`, constant-time cmp
15
+ * - unauthenticated boot logs a warn but is allowed for local dev
16
+ *
17
+ * Meaningful only under Node; RN builds should never import this
18
+ * module — the guard + `sideEffects: false` keep it out of prod
19
+ * bundles.
20
+ *
21
+ * Callers wire it as:
22
+ * const t = await createHttpTransport({ port: 8787, authToken: 'x' })
23
+ * await mcpServer.connect(t.transport)
24
+ * // ...
25
+ * await t.close()
26
+ */
27
+ import type { Server as HttpServer } from 'node:http';
28
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
29
+ import type { ToolsLogger } from '../../types';
30
+ export interface CreateHttpTransportConfig {
31
+ /** Listening port. `0` picks an ephemeral port (tests). Default `0`. */
32
+ port?: number;
33
+ /** URL path. Default `/mcp`. */
34
+ path?: string;
35
+ /**
36
+ * Optional bearer token. If set, incoming requests must present
37
+ * `Authorization: Bearer <authToken>` or get a 401. If unset, all
38
+ * requests pass — logged as a warn on boot.
39
+ */
40
+ authToken?: string;
41
+ logger?: ToolsLogger;
42
+ }
43
+ export interface HttpTransportHandle {
44
+ /** The SDK transport — hand to `mcpServer.connect(...)`. */
45
+ transport: Transport;
46
+ /** The Node HTTP server — exposed for advanced callers. */
47
+ server: HttpServer;
48
+ /** Actual listening port (resolved when `port: 0`). */
49
+ port: number;
50
+ /** Shut down the HTTP server + transport. */
51
+ close: () => Promise<void>;
52
+ }
53
+ export declare function createHttpTransport(config?: CreateHttpTransportConfig): Promise<HttpTransportHandle>;
54
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../../src/tools/mcp/transports/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAkB,MAAM,WAAW,CAAA;AAItF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAA;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,MAAM,WAAW,yBAAyB;IACxC,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,SAAS,EAAE,SAAS,CAAA;IACpB,2DAA2D;IAC3D,MAAM,EAAE,UAAU,CAAA;IAClB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,6CAA6C;IAC7C,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAgCD,wBAAsB,mBAAmB,CACvC,MAAM,GAAE,yBAA8B,GACrC,OAAO,CAAC,mBAAmB,CAAC,CAkF9B"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * `createHttpTransport` — Node HTTP transport for `createMcpServer`.
3
+ *
4
+ * Boots a bare `node:http` server that gates requests behind an
5
+ * optional bearer token, then hands them to the SDK's
6
+ * `StreamableHTTPServerTransport`. Runs in stateless mode
7
+ * (no `sessionIdGenerator`) — every request is self-contained, which
8
+ * matches how our tools are already wired (`handle.setContext(...)`
9
+ * carries the per-user scope, not the transport).
10
+ *
11
+ * Deliberately minimal:
12
+ * - one path (default `/mcp`)
13
+ * - one method (POST + GET both dispatched to the SDK)
14
+ * - bearer via `Authorization: Bearer <token>`, constant-time cmp
15
+ * - unauthenticated boot logs a warn but is allowed for local dev
16
+ *
17
+ * Meaningful only under Node; RN builds should never import this
18
+ * module — the guard + `sideEffects: false` keep it out of prod
19
+ * bundles.
20
+ *
21
+ * Callers wire it as:
22
+ * const t = await createHttpTransport({ port: 8787, authToken: 'x' })
23
+ * await mcpServer.connect(t.transport)
24
+ * // ...
25
+ * await t.close()
26
+ */
27
+ import { timingSafeEqual } from 'node:crypto';
28
+ import { createServer } from 'node:http';
29
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
30
+ const UNAUTHORIZED_BODY = 'Unauthorized\n';
31
+ const NOT_FOUND_BODY = 'Not Found\n';
32
+ function bearerMatches(header, expected) {
33
+ if (!header || !header.startsWith('Bearer '))
34
+ return false;
35
+ const presented = header.slice('Bearer '.length);
36
+ const a = Buffer.from(presented);
37
+ const b = Buffer.from(expected);
38
+ if (a.length !== b.length)
39
+ return false;
40
+ return timingSafeEqual(a, b);
41
+ }
42
+ async function readBody(req) {
43
+ return new Promise((resolve, reject) => {
44
+ const chunks = [];
45
+ req.on('data', (c) => chunks.push(c));
46
+ req.on('end', () => {
47
+ if (chunks.length === 0)
48
+ return resolve(undefined);
49
+ const raw = Buffer.concat(chunks).toString('utf8');
50
+ try {
51
+ resolve(raw.length > 0 ? JSON.parse(raw) : undefined);
52
+ }
53
+ catch (err) {
54
+ reject(err instanceof Error ? err : new Error(String(err)));
55
+ }
56
+ });
57
+ req.on('error', reject);
58
+ });
59
+ }
60
+ export async function createHttpTransport(config = {}) {
61
+ const port = config.port ?? 0;
62
+ const path = config.path ?? '/mcp';
63
+ const authToken = config.authToken;
64
+ if (!authToken) {
65
+ config.logger?.warn('MCP HTTP transport started without authToken — do not expose to untrusted networks');
66
+ }
67
+ const transport = new StreamableHTTPServerTransport({
68
+ sessionIdGenerator: undefined,
69
+ });
70
+ const server = createServer((req, res) => {
71
+ // Path check — only the configured path is dispatched.
72
+ const url = req.url ?? '';
73
+ const [reqPath] = url.split('?');
74
+ if (reqPath !== path) {
75
+ res.statusCode = 404;
76
+ res.setHeader('content-type', 'text/plain');
77
+ res.end(NOT_FOUND_BODY);
78
+ return;
79
+ }
80
+ // Bearer check.
81
+ if (authToken) {
82
+ const header = req.headers.authorization;
83
+ if (!bearerMatches(header, authToken)) {
84
+ res.statusCode = 401;
85
+ res.setHeader('content-type', 'text/plain');
86
+ res.setHeader('www-authenticate', 'Bearer');
87
+ res.end(UNAUTHORIZED_BODY);
88
+ return;
89
+ }
90
+ }
91
+ // Body parse (POST) then dispatch.
92
+ void (async () => {
93
+ try {
94
+ const body = req.method === 'POST' ? await readBody(req) : undefined;
95
+ await transport.handleRequest(req, res, body);
96
+ }
97
+ catch (err) {
98
+ config.logger?.error('MCP HTTP transport request handler threw', {
99
+ err: err instanceof Error ? err.message : String(err),
100
+ });
101
+ if (!res.headersSent) {
102
+ res.statusCode = 500;
103
+ res.setHeader('content-type', 'text/plain');
104
+ res.end('Internal Server Error\n');
105
+ }
106
+ else {
107
+ res.end();
108
+ }
109
+ }
110
+ })();
111
+ });
112
+ await new Promise((resolve, reject) => {
113
+ server.once('error', reject);
114
+ server.listen(port, () => {
115
+ server.off('error', reject);
116
+ resolve();
117
+ });
118
+ });
119
+ const address = server.address();
120
+ const resolvedPort = address?.port ?? port;
121
+ return {
122
+ transport,
123
+ server,
124
+ port: resolvedPort,
125
+ close: async () => {
126
+ await new Promise((resolve) => {
127
+ server.close(() => resolve());
128
+ });
129
+ await transport.close();
130
+ },
131
+ };
132
+ }
133
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../../src/tools/mcp/transports/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `createStdioTransport` — Node stdio transport for `createMcpServer`.
3
+ *
4
+ * Reads MCP framed messages off `process.stdin` and writes them to
5
+ * `process.stdout`. Meaningful only under Node; on Hermes there is no
6
+ * stdio pipe. Callers should gate construction behind
7
+ * `shouldStartMcpServer()` so this module is never imported in prod
8
+ * RN builds (and its `node:stream` peer disappears with the rest of
9
+ * the MCP subpath under `sideEffects: false`).
10
+ *
11
+ * The SDK's `StdioServerTransport` accepts optional `stdin` / `stdout`
12
+ * streams; we expose the same knobs for tests that want to pipe
13
+ * fixtures without touching real stdio.
14
+ */
15
+ import type { Readable, Writable } from 'node:stream';
16
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
17
+ export interface CreateStdioTransportOptions {
18
+ stdin?: Readable;
19
+ stdout?: Writable;
20
+ }
21
+ export declare function createStdioTransport(options?: CreateStdioTransportOptions): Transport;
22
+ //# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../../../src/tools/mcp/transports/stdio.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAA;AAE9E,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,MAAM,CAAC,EAAE,QAAQ,CAAA;CAClB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,SAAS,CAEX"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `createStdioTransport` — Node stdio transport for `createMcpServer`.
3
+ *
4
+ * Reads MCP framed messages off `process.stdin` and writes them to
5
+ * `process.stdout`. Meaningful only under Node; on Hermes there is no
6
+ * stdio pipe. Callers should gate construction behind
7
+ * `shouldStartMcpServer()` so this module is never imported in prod
8
+ * RN builds (and its `node:stream` peer disappears with the rest of
9
+ * the MCP subpath under `sideEffects: false`).
10
+ *
11
+ * The SDK's `StdioServerTransport` accepts optional `stdin` / `stdout`
12
+ * streams; we expose the same knobs for tests that want to pipe
13
+ * fixtures without touching real stdio.
14
+ */
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+ export function createStdioTransport(options = {}) {
17
+ return new StdioServerTransport(options.stdin, options.stdout);
18
+ }
19
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../../../src/tools/mcp/transports/stdio.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAQhF,MAAM,UAAU,oBAAoB,CAClC,UAAuC,EAAE;IAEzC,OAAO,IAAI,oBAAoB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;AAChE,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * In-memory token-bucket rate limiter for analytics tools.
3
+ *
4
+ * Three independent buckets per `(toolName, userId)`:
5
+ * - per-tool-per-minute
6
+ * - per-tool-per-hour
7
+ * - per-user-per-minute (across all tools)
8
+ *
9
+ * `check(toolName, userId)` refills every bucket by elapsed time,
10
+ * then attempts to spend one token from all three. If any lacks a
11
+ * token, returns `false` **without** spending from the others.
12
+ * Otherwise deducts one from each and returns `true`.
13
+ *
14
+ * Persistence is out of scope for v0.1.0 — process restart resets
15
+ * buckets. Audit persistence (T-09) handles the durable trail.
16
+ */
17
+ import { type RateLimitConfig } from './types';
18
+ export interface RateLimiter {
19
+ check: (toolName: string, userId: string) => boolean;
20
+ }
21
+ export interface CreateRateLimiterConfig {
22
+ config?: RateLimitConfig;
23
+ clock?: () => number;
24
+ }
25
+ export declare function createRateLimiter(cfg?: CreateRateLimiterConfig): RateLimiter;
26
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/tools/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAUlE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;CACrD;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB;AAKD,wBAAgB,iBAAiB,CAC/B,GAAG,GAAE,uBAA4B,GAChC,WAAW,CA6Eb"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * In-memory token-bucket rate limiter for analytics tools.
3
+ *
4
+ * Three independent buckets per `(toolName, userId)`:
5
+ * - per-tool-per-minute
6
+ * - per-tool-per-hour
7
+ * - per-user-per-minute (across all tools)
8
+ *
9
+ * `check(toolName, userId)` refills every bucket by elapsed time,
10
+ * then attempts to spend one token from all three. If any lacks a
11
+ * token, returns `false` **without** spending from the others.
12
+ * Otherwise deducts one from each and returns `true`.
13
+ *
14
+ * Persistence is out of scope for v0.1.0 — process restart resets
15
+ * buckets. Audit persistence (T-09) handles the durable trail.
16
+ */
17
+ import { DEFAULT_RATE_LIMIT } from './types';
18
+ const MS_PER_MINUTE = 60000;
19
+ const MS_PER_HOUR = 3600000;
20
+ export function createRateLimiter(cfg = {}) {
21
+ const rl = cfg.config ?? DEFAULT_RATE_LIMIT;
22
+ const clock = cfg.clock ?? Date.now;
23
+ const perToolMinute = new Map();
24
+ const perToolHour = new Map();
25
+ const perUserMinute = new Map();
26
+ function getOrCreate(map, key, capacity, windowMs, now) {
27
+ let b = map.get(key);
28
+ if (!b) {
29
+ b = {
30
+ tokens: capacity,
31
+ lastRefillMs: now,
32
+ capacity,
33
+ refillPerMs: capacity / windowMs,
34
+ };
35
+ map.set(key, b);
36
+ }
37
+ return b;
38
+ }
39
+ function refill(b, now) {
40
+ const elapsed = now - b.lastRefillMs;
41
+ if (elapsed <= 0)
42
+ return;
43
+ b.tokens = Math.min(b.capacity, b.tokens + elapsed * b.refillPerMs);
44
+ b.lastRefillMs = now;
45
+ }
46
+ return {
47
+ check(toolName, userId) {
48
+ const now = clock();
49
+ const toolKey = `${toolName}:${userId}`;
50
+ const userKey = userId;
51
+ const tm = getOrCreate(perToolMinute, toolKey, rl.perToolPerMinute, MS_PER_MINUTE, now);
52
+ const th = getOrCreate(perToolHour, toolKey, rl.perToolPerHour, MS_PER_HOUR, now);
53
+ const um = getOrCreate(perUserMinute, userKey, rl.perUserPerMinute, MS_PER_MINUTE, now);
54
+ refill(tm, now);
55
+ refill(th, now);
56
+ refill(um, now);
57
+ if (tm.tokens < 1 || th.tokens < 1 || um.tokens < 1) {
58
+ return false;
59
+ }
60
+ tm.tokens -= 1;
61
+ th.tokens -= 1;
62
+ um.tokens -= 1;
63
+ return true;
64
+ },
65
+ };
66
+ }
67
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/tools/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,kBAAkB,EAAwB,MAAM,SAAS,CAAA;AAmBlE,MAAM,aAAa,GAAG,KAAM,CAAA;AAC5B,MAAM,WAAW,GAAG,OAAS,CAAA;AAE7B,MAAM,UAAU,iBAAiB,CAC/B,MAA+B,EAAE;IAEjC,MAAM,EAAE,GAAoB,GAAG,CAAC,MAAM,IAAI,kBAAkB,CAAA;IAC5D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAA;IAEnC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAA;IACpD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuB,CAAA;IAClD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAA;IAEpD,SAAS,WAAW,CAClB,GAA6B,EAC7B,GAAW,EACX,QAAgB,EAChB,QAAgB,EAChB,GAAW;QAEX,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACpB,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG;gBACF,MAAM,EAAE,QAAQ;gBAChB,YAAY,EAAE,GAAG;gBACjB,QAAQ;gBACR,WAAW,EAAE,QAAQ,GAAG,QAAQ;aACjC,CAAA;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC;IAED,SAAS,MAAM,CAAC,CAAc,EAAE,GAAW;QACzC,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,CAAC,YAAY,CAAA;QACpC,IAAI,OAAO,IAAI,CAAC;YAAE,OAAM;QACxB,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC,WAAW,CAAC,CAAA;QACnE,CAAC,CAAC,YAAY,GAAG,GAAG,CAAA;IACtB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,QAAQ,EAAE,MAAM;YACpB,MAAM,GAAG,GAAG,KAAK,EAAE,CAAA;YACnB,MAAM,OAAO,GAAG,GAAG,QAAQ,IAAI,MAAM,EAAE,CAAA;YACvC,MAAM,OAAO,GAAG,MAAM,CAAA;YAEtB,MAAM,EAAE,GAAG,WAAW,CACpB,aAAa,EACb,OAAO,EACP,EAAE,CAAC,gBAAgB,EACnB,aAAa,EACb,GAAG,CACJ,CAAA;YACD,MAAM,EAAE,GAAG,WAAW,CACpB,WAAW,EACX,OAAO,EACP,EAAE,CAAC,cAAc,EACjB,WAAW,EACX,GAAG,CACJ,CAAA;YACD,MAAM,EAAE,GAAG,WAAW,CACpB,aAAa,EACb,OAAO,EACP,EAAE,CAAC,gBAAgB,EACnB,aAAa,EACb,GAAG,CACJ,CAAA;YAED,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YACf,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YACf,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YAEf,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpD,OAAO,KAAK,CAAA;YACd,CAAC;YAED,EAAE,CAAC,MAAM,IAAI,CAAC,CAAA;YACd,EAAE,CAAC,MAAM,IAAI,CAAC,CAAA;YACd,EAAE,CAAC,MAAM,IAAI,CAAC,CAAA;YACd,OAAO,IAAI,CAAA;QACb,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Public type surface for @mongrov/analytics/tools.
3
+ *
4
+ * Types shared across tool impls, rate limiter, authorize hooks,
5
+ * audit writer, wrapper, and the top-level factory.
6
+ */
7
+ import type { AnalyticsEngine, FamilyMembersProvider } from '../core/types';
8
+ /** Runtime context threaded through every tool impl and authorize hook. */
9
+ export interface ToolContext {
10
+ readonly requesterUserId: string;
11
+ readonly brand: string;
12
+ readonly familyId: string;
13
+ readonly now?: () => Date;
14
+ }
15
+ /** Bounded text summary returned by every tool impl. */
16
+ export interface ToolResult {
17
+ readonly text: string;
18
+ readonly rowCount: number;
19
+ readonly bytes: number;
20
+ }
21
+ /**
22
+ * Authorization hook — invoked before every tool execute. `false`
23
+ * rejects the call before SQL runs. Toolname passes through so hooks
24
+ * can gate by tool identity in addition to args + ctx.
25
+ */
26
+ export type AuthorizeFn = (toolName: string, args: Record<string, unknown>, ctx: ToolContext) => Promise<boolean>;
27
+ /**
28
+ * Token-bucket rate-limit ceilings. Defaults come from
29
+ * `analytics-ai-tools/spec.md` §Rate limiting.
30
+ */
31
+ export interface RateLimitConfig {
32
+ perToolPerMinute: number;
33
+ perToolPerHour: number;
34
+ perUserPerMinute: number;
35
+ }
36
+ export declare const DEFAULT_RATE_LIMIT: RateLimitConfig;
37
+ export interface ToolsLogger {
38
+ debug: (message: string, meta?: Record<string, unknown>) => void;
39
+ info: (message: string, meta?: Record<string, unknown>) => void;
40
+ warn: (message: string, meta?: Record<string, unknown>) => void;
41
+ error: (message: string, meta?: Record<string, unknown>) => void;
42
+ }
43
+ /**
44
+ * A tool impl. Takes a Zod-parsed input and the runtime context
45
+ * (augmented with the analytics engine), returns a bounded text
46
+ * result. `makeTool` (T-10) wraps this with rate → auth → execute →
47
+ * budget → audit chain to produce an AI SDK `tool()` handle.
48
+ */
49
+ export type ToolImpl<Input> = (input: Input, ctx: ToolContext & {
50
+ analytics: AnalyticsEngine;
51
+ }) => Promise<ToolResult>;
52
+ /**
53
+ * Outcome recorded in `tool_call_audit.outcome`. Enum discipline —
54
+ * every wrapper code path resolves to exactly one of these.
55
+ */
56
+ export type ToolOutcome = 'success' | 'rate_limited' | 'authorized_reject' | 'error';
57
+ /**
58
+ * A single row destined for `tool_call_audit`. Column mapping (see
59
+ * `core/schemas.ts` line 189):
60
+ * ts, brand, family_id, requester_user_id, tool_name, args,
61
+ * result_bytes, result_row_count, latency_ms, outcome, error_message
62
+ */
63
+ export interface AuditEntry {
64
+ readonly ts: Date;
65
+ readonly brand: string;
66
+ readonly familyId: string;
67
+ readonly requesterUserId: string;
68
+ readonly toolName: string;
69
+ readonly args: Record<string, unknown>;
70
+ readonly resultBytes: number | null;
71
+ readonly resultRowCount: number | null;
72
+ readonly latencyMs: number | null;
73
+ readonly outcome: ToolOutcome;
74
+ readonly errorMessage: string | null;
75
+ }
76
+ /**
77
+ * Batched writer for `tool_call_audit`. `record` is non-blocking —
78
+ * entries flush on batch-size or timer. `flush` and `close` are test
79
+ * hooks / graceful-shutdown hooks respectively.
80
+ */
81
+ export interface AuditWriter {
82
+ record: (entry: AuditEntry) => void;
83
+ flush: () => Promise<void>;
84
+ close: () => Promise<void>;
85
+ }
86
+ /**
87
+ * Per-tool output ceiling enforced by `applyOutputBudget`. Text
88
+ * exceeding `maxBytes` is UTF-8-safely truncated with a
89
+ * `\n[truncated]` suffix. `maxRows` is informational — impls limit
90
+ * row count via SQL.
91
+ */
92
+ export interface OutputBudget {
93
+ maxBytes: number;
94
+ maxRows: number;
95
+ }
96
+ export declare const DEFAULT_OUTPUT_BUDGET: OutputBudget;
97
+ /**
98
+ * Config for `createAnalyticsTools` factory. All fields except
99
+ * `analytics` are optional with sane defaults from `spec.md`.
100
+ *
101
+ * - `rateLimit: false` disables the limiter entirely (tests only).
102
+ * - `authorize: undefined` defaults to `familyScopeAuthorize` wired
103
+ * with `familyMembersProvider` if supplied.
104
+ * - `audit.enabled: false` makes `record` a no-op.
105
+ */
106
+ export interface AnalyticsToolsConfig {
107
+ analytics: AnalyticsEngine;
108
+ authorize?: AuthorizeFn;
109
+ rateLimit?: RateLimitConfig | false;
110
+ audit?: {
111
+ enabled?: boolean;
112
+ batchSize?: number;
113
+ flushIntervalMs?: number;
114
+ retentionDays?: number;
115
+ };
116
+ outputBudget?: Partial<OutputBudget>;
117
+ logger?: ToolsLogger;
118
+ familyMembersProvider?: FamilyMembersProvider;
119
+ clock?: () => number;
120
+ }
121
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Public type surface for @mongrov/analytics/tools.
3
+ *
4
+ * Types shared across tool impls, rate limiter, authorize hooks,
5
+ * audit writer, wrapper, and the top-level factory.
6
+ */
7
+ export const DEFAULT_RATE_LIMIT = {
8
+ perToolPerMinute: 20,
9
+ perToolPerHour: 200,
10
+ perUserPerMinute: 60,
11
+ };
12
+ export const DEFAULT_OUTPUT_BUDGET = {
13
+ maxBytes: 4096,
14
+ maxRows: 100,
15
+ };
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwCH,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,gBAAgB,EAAE,EAAE;IACpB,cAAc,EAAE,GAAG;IACnB,gBAAgB,EAAE,EAAE;CACrB,CAAA;AAwED,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,GAAG;CACb,CAAA"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `makeTool` — compose an AI SDK `tool()` from a raw impl plus the
3
+ * rate → auth → execute → budget → audit chain.
4
+ *
5
+ * The chain order is load-bearing:
6
+ * 1. Rate limit → cheap, no I/O; short-circuits abusive callers.
7
+ * 2. Authorize → may hit SQL/provider; runs after rate cap.
8
+ * 3. Execute → real tool work.
9
+ * 4. Budget → truncate oversized text before returning.
10
+ * 5. Audit → record every terminal state (success / reject
11
+ * / rate-limited / error).
12
+ *
13
+ * Every branch records exactly one audit entry. The wrapper never
14
+ * throws — errors resolve to a short LLM-friendly string. Because
15
+ * the AI SDK `execute(input)` signature has no per-call context arg,
16
+ * the caller supplies a `ctxProvider()` closure that returns the
17
+ * current `ToolContext` at invocation time (the factory backs this
18
+ * with a mutable container updated per request).
19
+ */
20
+ import type { ZodTypeAny } from 'zod';
21
+ import type { AnalyticsEngine } from '../core/types';
22
+ import type { RateLimiter } from './rate-limit';
23
+ import type { AuditWriter, AuthorizeFn, OutputBudget, ToolContext, ToolImpl, ToolsLogger } from './types';
24
+ export interface MakeToolConfig<Input> {
25
+ name: string;
26
+ description: string;
27
+ /**
28
+ * Accepts any Zod schema whose parsed output is assignable to
29
+ * `Input`. Widened to `ZodTypeAny` so schemas with `.default()` /
30
+ * `.optional()` fields (whose input types include `undefined`)
31
+ * still fit — the AI SDK's own `tool()` uses the same shape.
32
+ */
33
+ inputSchema: ZodTypeAny;
34
+ impl: ToolImpl<Input>;
35
+ analytics: AnalyticsEngine;
36
+ authorize?: AuthorizeFn;
37
+ rateLimiter?: RateLimiter | null;
38
+ audit: AuditWriter;
39
+ budget: OutputBudget;
40
+ logger?: ToolsLogger;
41
+ /**
42
+ * Returns the current `ToolContext` at execute-time. The factory
43
+ * backs this with a mutable container so a single tool handle
44
+ * threads per-request scope. Returning `null` triggers a clean
45
+ * error branch (no ctx → audit `error` outcome).
46
+ */
47
+ ctxProvider: () => ToolContext | null;
48
+ clock?: () => number;
49
+ }
50
+ export declare function makeTool<Input>(cfg: MakeToolConfig<Input>): import("ai").Tool<ZodTypeAny, string> & {
51
+ execute: (args: any, options: import("ai").ToolExecutionOptions) => PromiseLike<string>;
52
+ };
53
+ //# sourceMappingURL=wrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../src/tools/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,WAAW,EACZ,MAAM,SAAS,CAAA;AAOhB,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,WAAW,EAAE,UAAU,CAAA;IACvB,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;IACrB,SAAS,EAAE,eAAe,CAAA;IAC1B,SAAS,CAAC,EAAE,WAAW,CAAA;IACvB,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IAChC,KAAK,EAAE,WAAW,CAAA;IAClB,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CACrB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC;;EAqHzD"}