@armature-tech/mcp-analytics 0.2.5 → 0.4.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/README.md +16 -19
- package/SKILL.md +18 -26
- package/dist/cjs/emit.d.ts +1 -3
- package/dist/cjs/emit.js +7 -21
- package/dist/cjs/events.d.ts +6 -12
- package/dist/cjs/events.js +12 -16
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -2
- package/dist/cjs/recorder.js +1 -15
- package/dist/cjs/types.d.ts +1 -3
- package/dist/esm/emit.d.ts +1 -3
- package/dist/esm/emit.js +5 -17
- package/dist/esm/events.d.ts +6 -12
- package/dist/esm/events.js +12 -16
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/recorder.js +2 -16
- package/dist/esm/types.d.ts +1 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @armature-tech/mcp-analytics
|
|
2
2
|
|
|
3
|
-
Wrapper SDK that instruments MCP servers with analytics telemetry. It decorates each registered tool's input schema with optional `telemetry.*` fields, strips those fields before the original handler runs, and posts
|
|
3
|
+
Wrapper SDK that instruments MCP servers with analytics telemetry. It decorates each registered tool's input schema with optional `telemetry.*` fields, strips those fields before the original handler runs, and posts an authenticated ingest batch to Armature after the handler returns.
|
|
4
4
|
|
|
5
5
|
The SDK is a drop-in for any server built on `@modelcontextprotocol/sdk`'s `McpServer`. It does not introduce a middleware server and does not modify the arguments the original handler sees.
|
|
6
6
|
|
|
@@ -11,9 +11,13 @@ It also exposes recorder primitives for dispatcher-style MCP servers that hand-r
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
npm install @armature-tech/mcp-analytics @modelcontextprotocol/sdk zod
|
|
14
|
+
npx skills add armature-tech/mcp-analytics --global && npm install @armature-tech/mcp-analytics @modelcontextprotocol/sdk zod
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
Then ask your coding agent (Claude Code, Cursor, Codex, Gemini, …): *"install Armature analytics on this MCP server"*. The `install-mcp-analytics` skill ([`SKILL.md`](SKILL.md)) detects which of the four integration shapes fits your repo (registry-style `McpServer`, drop-in factory, dispatcher, or Mastra `MCPServer`), makes the edits, and verifies the wiring.
|
|
18
|
+
|
|
19
|
+
Not using an agent? Just run the `npm install` part and follow the [Quick start](#quick-start) below. The `skills` CLI in the first half of the command is the open [`vercel-labs/skills`](https://github.com/vercel-labs/skills) installer — it copies `SKILL.md` into your agent's skill directory.
|
|
20
|
+
|
|
17
21
|
## Quick start
|
|
18
22
|
|
|
19
23
|
Two ways to wire this in, depending on whether you're adopting it on a new server or layering it on top of an existing `McpServer` factory.
|
|
@@ -27,8 +31,7 @@ import { z } from "zod";
|
|
|
27
31
|
const analytics = createAnalyticsRecorder({
|
|
28
32
|
armature: {
|
|
29
33
|
endpointUrl: "https://app.armature.tech/api/mcp-analytics/ingest",
|
|
30
|
-
|
|
31
|
-
ingestSecret: process.env.ANALYTICS_INGEST_SECRET,
|
|
34
|
+
apiKey: process.env.ANALYTICS_INGEST_API_KEY,
|
|
32
35
|
},
|
|
33
36
|
});
|
|
34
37
|
|
|
@@ -60,8 +63,7 @@ const server = createMcpAnalyticsServer(
|
|
|
60
63
|
{
|
|
61
64
|
armature: {
|
|
62
65
|
endpointUrl: "https://app.armature.tech/api/mcp-analytics/ingest",
|
|
63
|
-
|
|
64
|
-
ingestSecret: process.env.ANALYTICS_INGEST_SECRET,
|
|
66
|
+
apiKey: process.env.ANALYTICS_INGEST_API_KEY,
|
|
65
67
|
},
|
|
66
68
|
},
|
|
67
69
|
);
|
|
@@ -79,8 +81,7 @@ import { createAnalyticsRecorder } from "@armature-tech/mcp-analytics";
|
|
|
79
81
|
const analytics = createAnalyticsRecorder({
|
|
80
82
|
armature: {
|
|
81
83
|
endpointUrl: "https://app.armature.tech/api/mcp-analytics/ingest",
|
|
82
|
-
|
|
83
|
-
ingestSecret: process.env.ANALYTICS_INGEST_SECRET,
|
|
84
|
+
apiKey: process.env.ANALYTICS_INGEST_API_KEY,
|
|
84
85
|
delivery: "await",
|
|
85
86
|
actorId: ({ ctx }) => (ctx as RequestContext).userProfileId,
|
|
86
87
|
},
|
|
@@ -128,8 +129,7 @@ new MCPServer({
|
|
|
128
129
|
tools: wrapMastraTools(createMyTools(), {
|
|
129
130
|
armature: {
|
|
130
131
|
endpointUrl: "https://app.armature.tech/api/mcp-analytics/ingest",
|
|
131
|
-
|
|
132
|
-
ingestSecret: process.env.ANALYTICS_INGEST_SECRET,
|
|
132
|
+
apiKey: process.env.ANALYTICS_INGEST_API_KEY,
|
|
133
133
|
delivery: "await",
|
|
134
134
|
},
|
|
135
135
|
}),
|
|
@@ -225,7 +225,7 @@ The `telemetry` object is stripped before your handler runs. Your tool receives
|
|
|
225
225
|
|
|
226
226
|
### What Armature receives
|
|
227
227
|
|
|
228
|
-
After each tool call (success or error), the SDK posts
|
|
228
|
+
After each tool call (success or error), the SDK posts an authenticated batch to `endpointUrl` containing a `tool_call` event with timing, status, the input/output previews, and the telemetry fields the agent supplied. The first event for a new `sessionId` is preceded by a `session_init` event.
|
|
229
229
|
|
|
230
230
|
## Configuration
|
|
231
231
|
|
|
@@ -236,8 +236,7 @@ type McpAnalyticsConfig = {
|
|
|
236
236
|
};
|
|
237
237
|
armature?: {
|
|
238
238
|
endpointUrl?: string; // default reads ANALYTICS_INGEST_URL
|
|
239
|
-
|
|
240
|
-
ingestSecret?: string; // default reads ANALYTICS_INGEST_SECRET
|
|
239
|
+
apiKey?: string; // default reads ANALYTICS_INGEST_API_KEY
|
|
241
240
|
actorId?: string | ((input) => string | Promise<string>);
|
|
242
241
|
enabled?: boolean; // default true
|
|
243
242
|
delivery?: "background" | "await"; // default "background"
|
|
@@ -250,18 +249,17 @@ type McpAnalyticsConfig = {
|
|
|
250
249
|
|
|
251
250
|
Notes:
|
|
252
251
|
|
|
253
|
-
- If `
|
|
252
|
+
- If `apiKey` is missing, the SDK silently skips delivery — useful for local development.
|
|
254
253
|
- `delivery: "background"` (default) returns the tool result immediately and posts the batch on `setImmediate`. Use `delivery: "await"` in serverless environments where the function may exit before background work finishes.
|
|
255
|
-
- The actor id is a SHA-256 of
|
|
256
|
-
-
|
|
254
|
+
- The actor id is a SHA-256 of the actor seed. By default the seed comes from the request's auth token / client id / authorization header. Pass a static `armature.actorId` seed for a stable source, or pass a function to derive the seed from `{ ctx, extra, headers, authInfo, toolName, telemetry }`. Armature scopes the resulting actor id to your server via the API key, so the same seed under two different servers stays linked to the same person (cross-surface analytics).
|
|
255
|
+
- Each batch is POSTed with `Authorization: Bearer <apiKey>`. The server identity is resolved from the API key — no separate header.
|
|
257
256
|
|
|
258
257
|
## Environment variables
|
|
259
258
|
|
|
260
259
|
| Variable | Purpose |
|
|
261
260
|
| --- | --- |
|
|
262
261
|
| `ANALYTICS_INGEST_URL` | Ingest endpoint (defaults to the local mock at `http://127.0.0.1:8787/api/mcp-analytics/ingest`) |
|
|
263
|
-
| `
|
|
264
|
-
| `ANALYTICS_INGEST_SECRET` | Shared secret used to sign each batch |
|
|
262
|
+
| `ANALYTICS_INGEST_API_KEY` | Your Armature API key — identifies the MCP server and signs each batch |
|
|
265
263
|
|
|
266
264
|
## Lower-level exports
|
|
267
265
|
|
|
@@ -272,7 +270,6 @@ For custom integrations the package also exports building blocks used internally
|
|
|
272
270
|
- `decorateInputSchemaWithTelemetry(schema, config)` / `createTelemetryInputSchema(config)` / `createTelemetryJsonSchema(config)`
|
|
273
271
|
- `extractTelemetryArguments(args)` — split `{ telemetry, ...args }`
|
|
274
272
|
- `buildToolCallEvent(...)`, `buildActorId(...)`, `buildEventId(...)`
|
|
275
|
-
- `signIngestBody(body, secret, timestamp)`
|
|
276
273
|
- `postTelemetryEvent(batch, config)` / `emitTelemetryEvent(batch, config)`
|
|
277
274
|
- `defaultMcpAnalyticsConfig`
|
|
278
275
|
|
package/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: >
|
|
|
7
7
|
"instrument my tools", "wire mcp-analytics into our server". Detects which integration
|
|
8
8
|
shape fits the repo (registry-style McpServer, drop-in factory, dispatcher, or Mastra
|
|
9
9
|
MCPServer), makes the edits, and verifies the wiring by checking the schema includes
|
|
10
|
-
the telemetry block and a test tool call produces
|
|
10
|
+
the telemetry block and a test tool call produces an authenticated batch.
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Install @armature-tech/mcp-analytics into an MCP server
|
|
@@ -15,7 +15,7 @@ description: >
|
|
|
15
15
|
You are integrating the `@armature-tech/mcp-analytics` SDK into a customer's MCP server
|
|
16
16
|
codebase. The SDK decorates each tool's input schema with a `telemetry.*` block (so the
|
|
17
17
|
agent can pass `intent`, `context`, `frustration_level`), strips those fields before the
|
|
18
|
-
handler runs, and posts
|
|
18
|
+
handler runs, and posts an authenticated batch to Armature after each call.
|
|
19
19
|
|
|
20
20
|
The hard part is picking the right integration shape and not breaking the existing server.
|
|
21
21
|
Four shapes exist; pick one based on how the customer's code looks today.
|
|
@@ -50,31 +50,24 @@ npm install @armature-tech/mcp-analytics
|
|
|
50
50
|
If they aren't, install them too. Use the customer's package manager (check for
|
|
51
51
|
`pnpm-lock.yaml` / `yarn.lock` / `bun.lockb` and match it).
|
|
52
52
|
|
|
53
|
-
The package is published to
|
|
54
|
-
|
|
53
|
+
The package is published to the public npm registry — `npm install` works without any
|
|
54
|
+
`.npmrc` configuration.
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
@armature-tech:registry=https://npm.pkg.github.com
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Mention this once; don't litter the project with auth instructions.
|
|
61
|
-
|
|
62
|
-
## Step 3: Add the three environment variables
|
|
56
|
+
## Step 3: Add the API key environment variable
|
|
63
57
|
|
|
64
|
-
The SDK needs:
|
|
58
|
+
The SDK needs one credential, plus an optional URL override:
|
|
65
59
|
|
|
66
60
|
| Variable | What it is |
|
|
67
61
|
| --- | --- |
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `ANALYTICS_INGEST_SECRET` | The shared secret from the Armature dashboard |
|
|
62
|
+
| `ANALYTICS_INGEST_API_KEY` | Your Armature API key (created in the dashboard). Identifies the MCP server and signs each batch. |
|
|
63
|
+
| `ANALYTICS_INGEST_URL` | Optional. Defaults to the prod endpoint `https://app.armature.tech/api/mcp-analytics/ingest`. Override for a local mock or staging environment. |
|
|
71
64
|
|
|
72
|
-
Add
|
|
73
|
-
`vercel.json`, fly secrets, k8s manifests). Do **not** commit real values;
|
|
74
|
-
in `.env.example` and tell the user where to paste the real
|
|
65
|
+
Add `ANALYTICS_INGEST_API_KEY` to whatever env mechanism the project uses (`.env.example`,
|
|
66
|
+
`wrangler.toml`, `vercel.json`, fly secrets, k8s manifests). Do **not** commit real values;
|
|
67
|
+
put a placeholder in `.env.example` and tell the user where to paste the real one.
|
|
75
68
|
|
|
76
|
-
If
|
|
77
|
-
|
|
69
|
+
If `ANALYTICS_INGEST_API_KEY` is missing at runtime, the SDK silently no-ops. That's intentional
|
|
70
|
+
for local dev — say so once, don't add guards.
|
|
78
71
|
|
|
79
72
|
## Step 4: Pick a delivery mode
|
|
80
73
|
|
|
@@ -109,7 +102,7 @@ const server = createMcpAnalyticsServer(
|
|
|
109
102
|
() => createMyMcpServer(),
|
|
110
103
|
{
|
|
111
104
|
armature: {
|
|
112
|
-
// endpointUrl /
|
|
105
|
+
// endpointUrl / apiKey default to env vars
|
|
113
106
|
delivery: "await", // or "background" — see Step 4
|
|
114
107
|
},
|
|
115
108
|
},
|
|
@@ -300,7 +293,7 @@ common cause: tools registered outside the factory in Shape A).
|
|
|
300
293
|
- Set `armature.emit` in the config to a stub that captures the batch, fire a test tool
|
|
301
294
|
call, and assert the captured batch has one `tool_call` event with the right tool name.
|
|
302
295
|
|
|
303
|
-
A passing typecheck is not verification. The schema decoration and the
|
|
296
|
+
A passing typecheck is not verification. The schema decoration and the authenticated batch are
|
|
304
297
|
what matter — verify both.
|
|
305
298
|
|
|
306
299
|
## Step 7: Mention the gotchas, then stop
|
|
@@ -308,11 +301,10 @@ what matter — verify both.
|
|
|
308
301
|
Tell the user, briefly:
|
|
309
302
|
|
|
310
303
|
- `delivery: "background"` drops batches in serverless. You picked `"await"` (or not — say which).
|
|
311
|
-
- The SDK no-ops silently if
|
|
312
|
-
- The package is on GitHub Packages, so CI needs the `.npmrc` line from Step 2 with a `NODE_AUTH_TOKEN`.
|
|
304
|
+
- The SDK no-ops silently if `ANALYTICS_INGEST_API_KEY` is missing. Set it in prod.
|
|
313
305
|
|
|
314
306
|
Don't pad with anything else. End with one line: what you changed and what the user needs
|
|
315
|
-
to do (paste the
|
|
307
|
+
to do (paste the API key, deploy).
|
|
316
308
|
|
|
317
309
|
## What NOT to do
|
|
318
310
|
|
|
@@ -320,7 +312,7 @@ to do (paste the secrets, deploy).
|
|
|
320
312
|
via `onError`. Wrapping it adds noise.
|
|
321
313
|
- Don't add a `try/catch` around `flush()` either. Pass `onError` in config if the user
|
|
322
314
|
wants custom handling.
|
|
323
|
-
- Don't expose
|
|
315
|
+
- Don't expose `ANALYTICS_INGEST_API_KEY` to the client side — it's server-only. If you see it
|
|
324
316
|
imported in a browser bundle path, stop and flag it.
|
|
325
317
|
- Don't rewrite tool definitions to "match the SDK style" if Shape A works. Minimum
|
|
326
318
|
change wins.
|
package/dist/cjs/emit.d.ts
CHANGED
|
@@ -10,10 +10,8 @@ export declare const defaultMcpAnalyticsConfig: {
|
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
12
|
export declare const resolveEndpointUrl: (config: McpAnalyticsConfig) => string;
|
|
13
|
-
export declare const
|
|
14
|
-
export declare const resolveMcpServerId: (config: McpAnalyticsConfig) => string | undefined;
|
|
13
|
+
export declare const resolveApiKey: (config: McpAnalyticsConfig) => string | undefined;
|
|
15
14
|
export declare const resolveActorSeed: (config: McpAnalyticsConfig, input: ActorIdResolverInput) => Promise<string>;
|
|
16
|
-
export declare const signIngestBody: (body: string, secret: string, timestamp: string) => string;
|
|
17
15
|
export declare const postTelemetryEvent: (batch: AnalyticsIngestBatch, config?: McpAnalyticsConfig) => Promise<{
|
|
18
16
|
skipped: boolean;
|
|
19
17
|
reason: string;
|
package/dist/cjs/emit.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createFlushableEmitter = exports.emitTelemetryEvent = exports.postTelemetryEvent = exports.
|
|
4
|
-
const node_crypto_1 = require("node:crypto");
|
|
3
|
+
exports.createFlushableEmitter = exports.emitTelemetryEvent = exports.postTelemetryEvent = exports.resolveActorSeed = exports.resolveApiKey = exports.resolveEndpointUrl = exports.defaultMcpAnalyticsConfig = void 0;
|
|
5
4
|
const utils_js_1 = require("./utils.js");
|
|
6
5
|
exports.defaultMcpAnalyticsConfig = {
|
|
7
6
|
telemetry: {
|
|
@@ -19,14 +18,10 @@ const resolveEndpointUrl = (config) => {
|
|
|
19
18
|
exports.defaultMcpAnalyticsConfig.armature.endpointUrl;
|
|
20
19
|
};
|
|
21
20
|
exports.resolveEndpointUrl = resolveEndpointUrl;
|
|
22
|
-
const
|
|
23
|
-
return config.armature?.
|
|
21
|
+
const resolveApiKey = (config) => {
|
|
22
|
+
return config.armature?.apiKey ?? (0, utils_js_1.readEnv)("ANALYTICS_INGEST_API_KEY");
|
|
24
23
|
};
|
|
25
|
-
exports.
|
|
26
|
-
const resolveMcpServerId = (config) => {
|
|
27
|
-
return config.armature?.mcpServerId ?? (0, utils_js_1.readEnv)("ANALYTICS_MCP_SERVER_ID");
|
|
28
|
-
};
|
|
29
|
-
exports.resolveMcpServerId = resolveMcpServerId;
|
|
24
|
+
exports.resolveApiKey = resolveApiKey;
|
|
30
25
|
const resolveActorSeed = async (config, input) => {
|
|
31
26
|
const configuredActorId = config.armature?.actorId;
|
|
32
27
|
if (typeof configuredActorId === "function") {
|
|
@@ -44,20 +39,13 @@ const resolveActorSeed = async (config, input) => {
|
|
|
44
39
|
return "anonymous";
|
|
45
40
|
};
|
|
46
41
|
exports.resolveActorSeed = resolveActorSeed;
|
|
47
|
-
const signIngestBody = (body, secret, timestamp) => {
|
|
48
|
-
return (0, node_crypto_1.createHmac)("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
|
|
49
|
-
};
|
|
50
|
-
exports.signIngestBody = signIngestBody;
|
|
51
42
|
const postTelemetryEvent = async (batch, config = exports.defaultMcpAnalyticsConfig) => {
|
|
52
43
|
const endpointUrl = (0, exports.resolveEndpointUrl)(config);
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
if (!ingestSecret || !mcpServerId) {
|
|
44
|
+
const apiKey = (0, exports.resolveApiKey)(config);
|
|
45
|
+
if (!apiKey) {
|
|
56
46
|
return { skipped: true, reason: "ingest_config_missing" };
|
|
57
47
|
}
|
|
58
48
|
const body = JSON.stringify(batch);
|
|
59
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
60
|
-
const signature = (0, exports.signIngestBody)(body, ingestSecret, timestamp);
|
|
61
49
|
const controller = new AbortController();
|
|
62
50
|
const timeout = setTimeout(() => controller.abort(), config.armature?.timeoutMs ?? exports.defaultMcpAnalyticsConfig.armature.timeoutMs);
|
|
63
51
|
try {
|
|
@@ -65,9 +53,7 @@ const postTelemetryEvent = async (batch, config = exports.defaultMcpAnalyticsCon
|
|
|
65
53
|
method: "POST",
|
|
66
54
|
headers: {
|
|
67
55
|
"Content-Type": "application/json",
|
|
68
|
-
|
|
69
|
-
"X-Armature-Timestamp": timestamp,
|
|
70
|
-
"X-Armature-Signature": signature,
|
|
56
|
+
Authorization: `Bearer ${apiKey}`,
|
|
71
57
|
},
|
|
72
58
|
body,
|
|
73
59
|
signal: controller.signal,
|
package/dist/cjs/events.d.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import type { AnalyticsEventKind, AnalyticsIngestBatch, AnalyticsIngestEvent, McpClientInfo, RequestExtra, TelemetryArgs } from "./types.js";
|
|
2
|
-
export declare const buildActorId: ({
|
|
3
|
-
mcpServerId: string;
|
|
2
|
+
export declare const buildActorId: ({ actorSeed, }: {
|
|
4
3
|
actorSeed: string;
|
|
5
4
|
}) => string;
|
|
6
|
-
export declare const buildEventId: ({
|
|
7
|
-
mcpServerId: string;
|
|
5
|
+
export declare const buildEventId: ({ actorId, requestId, kind, }: {
|
|
8
6
|
actorId: string;
|
|
9
7
|
requestId: string;
|
|
10
8
|
kind: AnalyticsEventKind;
|
|
11
9
|
}) => string;
|
|
12
|
-
export declare const buildToolCallEvent: ({ toolName, telemetry, input, output, status, durationMs, errorMessage,
|
|
10
|
+
export declare const buildToolCallEvent: ({ toolName, telemetry, input, output, status, durationMs, errorMessage, actorId, sessionId, requestId, startedAt, finishedAt, }: {
|
|
13
11
|
toolName: string;
|
|
14
12
|
telemetry?: TelemetryArgs;
|
|
15
13
|
input: unknown;
|
|
@@ -17,15 +15,13 @@ export declare const buildToolCallEvent: ({ toolName, telemetry, input, output,
|
|
|
17
15
|
status: "ok" | "error";
|
|
18
16
|
durationMs: number;
|
|
19
17
|
errorMessage?: string;
|
|
20
|
-
mcpServerId: string;
|
|
21
18
|
actorId: string;
|
|
22
19
|
sessionId?: string;
|
|
23
20
|
requestId: string;
|
|
24
21
|
startedAt: string;
|
|
25
22
|
finishedAt: string;
|
|
26
23
|
}) => AnalyticsIngestEvent;
|
|
27
|
-
export declare const buildSessionInitEvent: ({
|
|
28
|
-
mcpServerId: string;
|
|
24
|
+
export declare const buildSessionInitEvent: ({ actorId, sessionId, requestId, startedAt, extra, clientInfo, }: {
|
|
29
25
|
actorId: string;
|
|
30
26
|
sessionId: string;
|
|
31
27
|
requestId: string;
|
|
@@ -33,17 +29,15 @@ export declare const buildSessionInitEvent: ({ mcpServerId, actorId, sessionId,
|
|
|
33
29
|
extra?: RequestExtra;
|
|
34
30
|
clientInfo?: McpClientInfo;
|
|
35
31
|
}) => AnalyticsIngestEvent;
|
|
36
|
-
export declare const buildBatch: ({ event, extra,
|
|
32
|
+
export declare const buildBatch: ({ event, extra, actorId, startedAt, sessionInitKeys, clientInfo, }: {
|
|
37
33
|
event: AnalyticsIngestEvent;
|
|
38
34
|
extra?: RequestExtra;
|
|
39
|
-
mcpServerId: string;
|
|
40
35
|
actorId: string;
|
|
41
36
|
startedAt: string;
|
|
42
37
|
sessionInitKeys: Set<string>;
|
|
43
38
|
clientInfo?: McpClientInfo;
|
|
44
39
|
}) => AnalyticsIngestBatch;
|
|
45
|
-
export declare const buildSessionInitBatch: ({
|
|
46
|
-
mcpServerId: string;
|
|
40
|
+
export declare const buildSessionInitBatch: ({ actorId, sessionId, requestId, startedAt, extra, sessionInitKeys, clientInfo, }: {
|
|
47
41
|
actorId: string;
|
|
48
42
|
sessionId: string;
|
|
49
43
|
requestId: string;
|
package/dist/cjs/events.js
CHANGED
|
@@ -21,27 +21,26 @@ const capCapabilities = (capabilities) => {
|
|
|
21
21
|
}
|
|
22
22
|
return capabilities;
|
|
23
23
|
};
|
|
24
|
-
const buildActorId = ({
|
|
25
|
-
return (0, utils_js_1.sha256Hex)(
|
|
24
|
+
const buildActorId = ({ actorSeed, }) => {
|
|
25
|
+
return (0, utils_js_1.sha256Hex)(actorSeed);
|
|
26
26
|
};
|
|
27
27
|
exports.buildActorId = buildActorId;
|
|
28
|
-
const buildEventId = ({
|
|
29
|
-
return (0, utils_js_1.sha256Hex)(`${
|
|
28
|
+
const buildEventId = ({ actorId, requestId, kind, }) => {
|
|
29
|
+
return (0, utils_js_1.sha256Hex)(`${actorId} ${kind} ${requestId}`);
|
|
30
30
|
};
|
|
31
31
|
exports.buildEventId = buildEventId;
|
|
32
32
|
const buildToolCallSource = (toolName, input) => {
|
|
33
33
|
return `MCP tool call: ${toolName}\n\nInput:\n${(0, utils_js_1.stringifyPreview)(input)}`;
|
|
34
34
|
};
|
|
35
|
-
const buildToolCallEvent = ({ toolName, telemetry, input, output, status, durationMs, errorMessage,
|
|
35
|
+
const buildToolCallEvent = ({ toolName, telemetry, input, output, status, durationMs, errorMessage, actorId, sessionId, requestId, startedAt, finishedAt, }) => {
|
|
36
36
|
const inputPreview = (0, utils_js_1.truncateUtf8)((0, utils_js_1.stringifyPreview)(input), utils_js_1.MAX_PREVIEW_BYTES);
|
|
37
37
|
const source = (0, utils_js_1.truncateUtf8)(buildToolCallSource(toolName, input), utils_js_1.MAX_SOURCE_BYTES);
|
|
38
38
|
const resultPreview = output === undefined
|
|
39
39
|
? null
|
|
40
40
|
: (0, utils_js_1.truncateUtf8)((0, utils_js_1.stringifyPreview)(output), utils_js_1.MAX_PREVIEW_BYTES);
|
|
41
41
|
return {
|
|
42
|
-
event_id: (0, exports.buildEventId)({
|
|
42
|
+
event_id: (0, exports.buildEventId)({ actorId, requestId, kind: "tool_call" }),
|
|
43
43
|
kind: "tool_call",
|
|
44
|
-
mcp_server_id: mcpServerId,
|
|
45
44
|
actor_id: actorId,
|
|
46
45
|
session_id_hint: sessionId ?? null,
|
|
47
46
|
started_at: startedAt,
|
|
@@ -66,11 +65,10 @@ const buildToolCallEvent = ({ toolName, telemetry, input, output, status, durati
|
|
|
66
65
|
};
|
|
67
66
|
};
|
|
68
67
|
exports.buildToolCallEvent = buildToolCallEvent;
|
|
69
|
-
const buildSessionInitEvent = ({
|
|
68
|
+
const buildSessionInitEvent = ({ actorId, sessionId, requestId, startedAt, extra, clientInfo, }) => {
|
|
70
69
|
return {
|
|
71
|
-
event_id: (0, exports.buildEventId)({
|
|
70
|
+
event_id: (0, exports.buildEventId)({ actorId, requestId, kind: "session_init" }),
|
|
72
71
|
kind: "session_init",
|
|
73
|
-
mcp_server_id: mcpServerId,
|
|
74
72
|
actor_id: actorId,
|
|
75
73
|
session_id_hint: sessionId,
|
|
76
74
|
started_at: startedAt,
|
|
@@ -97,14 +95,13 @@ const buildSessionInitEvent = ({ mcpServerId, actorId, sessionId, requestId, sta
|
|
|
97
95
|
};
|
|
98
96
|
};
|
|
99
97
|
exports.buildSessionInitEvent = buildSessionInitEvent;
|
|
100
|
-
const buildBatch = ({ event, extra,
|
|
98
|
+
const buildBatch = ({ event, extra, actorId, startedAt, sessionInitKeys, clientInfo, }) => {
|
|
101
99
|
const events = [];
|
|
102
100
|
if (extra?.sessionId) {
|
|
103
|
-
const key = `${
|
|
101
|
+
const key = `${actorId}:${extra.sessionId}`;
|
|
104
102
|
if (!sessionInitKeys.has(key)) {
|
|
105
103
|
sessionInitKeys.add(key);
|
|
106
104
|
events.push((0, exports.buildSessionInitEvent)({
|
|
107
|
-
mcpServerId,
|
|
108
105
|
actorId,
|
|
109
106
|
sessionId: extra.sessionId,
|
|
110
107
|
requestId: `${event.event_id}:session_init`,
|
|
@@ -118,8 +115,8 @@ const buildBatch = ({ event, extra, mcpServerId, actorId, startedAt, sessionInit
|
|
|
118
115
|
return { schema_version: utils_js_1.SCHEMA_VERSION, events };
|
|
119
116
|
};
|
|
120
117
|
exports.buildBatch = buildBatch;
|
|
121
|
-
const buildSessionInitBatch = ({
|
|
122
|
-
const key = `${
|
|
118
|
+
const buildSessionInitBatch = ({ actorId, sessionId, requestId, startedAt, extra, sessionInitKeys, clientInfo, }) => {
|
|
119
|
+
const key = `${actorId}:${sessionId}`;
|
|
123
120
|
if (sessionInitKeys.has(key))
|
|
124
121
|
return null;
|
|
125
122
|
sessionInitKeys.add(key);
|
|
@@ -127,7 +124,6 @@ const buildSessionInitBatch = ({ mcpServerId, actorId, sessionId, requestId, sta
|
|
|
127
124
|
schema_version: utils_js_1.SCHEMA_VERSION,
|
|
128
125
|
events: [
|
|
129
126
|
(0, exports.buildSessionInitEvent)({
|
|
130
|
-
mcpServerId,
|
|
131
127
|
actorId,
|
|
132
128
|
sessionId,
|
|
133
129
|
requestId,
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { ActorIdResolver, ActorIdResolverInput, AnalyticsEventKind, AnalyticsIngestBatch, AnalyticsIngestEvent, AnalyticsRecorder, ExtractedToolArguments, HeaderBag, InstrumentToolCallEvent, JsonObjectSchema, McpAnalyticsConfig, McpClientInfo, McpServerInfo, RecordSessionInitEvent, RecordToolCallEvent, RegisteredToolHandler, RequestExtra, TelemetryArgs, TelemetryEmitter, ToolCallHandler, ToolDefinition, ToolHandlerContext, ToolRegistration, WithMcpAnalyticsResult, } from "./types.js";
|
|
2
2
|
export { createTelemetryInputSchema, createTelemetryJsonSchema, decorateInputSchemaWithTelemetry, extractTelemetryArguments, } from "./schema.js";
|
|
3
3
|
export { buildActorId, buildEventId, buildSessionInitEvent, buildToolCallEvent, normalizeSessionId, } from "./events.js";
|
|
4
|
-
export { defaultMcpAnalyticsConfig, emitTelemetryEvent, postTelemetryEvent,
|
|
4
|
+
export { defaultMcpAnalyticsConfig, emitTelemetryEvent, postTelemetryEvent, } from "./emit.js";
|
|
5
5
|
export { createAnalyticsRecorder } from "./recorder.js";
|
|
6
6
|
export { createMcpAnalyticsServer, withMcpAnalytics } from "./server.js";
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.withMcpAnalytics = exports.createMcpAnalyticsServer = exports.createAnalyticsRecorder = exports.
|
|
3
|
+
exports.withMcpAnalytics = exports.createMcpAnalyticsServer = exports.createAnalyticsRecorder = exports.postTelemetryEvent = exports.emitTelemetryEvent = exports.defaultMcpAnalyticsConfig = exports.normalizeSessionId = exports.buildToolCallEvent = exports.buildSessionInitEvent = exports.buildEventId = exports.buildActorId = exports.extractTelemetryArguments = exports.decorateInputSchemaWithTelemetry = exports.createTelemetryJsonSchema = exports.createTelemetryInputSchema = void 0;
|
|
4
4
|
var schema_js_1 = require("./schema.js");
|
|
5
5
|
Object.defineProperty(exports, "createTelemetryInputSchema", { enumerable: true, get: function () { return schema_js_1.createTelemetryInputSchema; } });
|
|
6
6
|
Object.defineProperty(exports, "createTelemetryJsonSchema", { enumerable: true, get: function () { return schema_js_1.createTelemetryJsonSchema; } });
|
|
@@ -16,7 +16,6 @@ var emit_js_1 = require("./emit.js");
|
|
|
16
16
|
Object.defineProperty(exports, "defaultMcpAnalyticsConfig", { enumerable: true, get: function () { return emit_js_1.defaultMcpAnalyticsConfig; } });
|
|
17
17
|
Object.defineProperty(exports, "emitTelemetryEvent", { enumerable: true, get: function () { return emit_js_1.emitTelemetryEvent; } });
|
|
18
18
|
Object.defineProperty(exports, "postTelemetryEvent", { enumerable: true, get: function () { return emit_js_1.postTelemetryEvent; } });
|
|
19
|
-
Object.defineProperty(exports, "signIngestBody", { enumerable: true, get: function () { return emit_js_1.signIngestBody; } });
|
|
20
19
|
var recorder_js_1 = require("./recorder.js");
|
|
21
20
|
Object.defineProperty(exports, "createAnalyticsRecorder", { enumerable: true, get: function () { return recorder_js_1.createAnalyticsRecorder; } });
|
|
22
21
|
var server_js_1 = require("./server.js");
|
package/dist/cjs/recorder.js
CHANGED
|
@@ -45,15 +45,8 @@ const appendTelemetryHint = (description) => {
|
|
|
45
45
|
return `${description}${TELEMETRY_DESCRIPTION_HINT}`;
|
|
46
46
|
};
|
|
47
47
|
const createAnalyticsContext = async (config, input) => {
|
|
48
|
-
const mcpServerId = (0, emit_js_1.resolveMcpServerId)(config);
|
|
49
|
-
if (!mcpServerId)
|
|
50
|
-
return null;
|
|
51
48
|
const actorSeed = await (0, emit_js_1.resolveActorSeed)(config, input);
|
|
52
|
-
|
|
53
|
-
mcpServerId,
|
|
54
|
-
actorSeed,
|
|
55
|
-
});
|
|
56
|
-
return { mcpServerId, actorId };
|
|
49
|
+
return { actorId: (0, events_js_1.buildActorId)({ actorSeed }) };
|
|
57
50
|
};
|
|
58
51
|
const createAnalyticsRecorder = (config = emit_js_1.defaultMcpAnalyticsConfig) => {
|
|
59
52
|
const { emitBatch, flush } = (0, emit_js_1.createFlushableEmitter)(config);
|
|
@@ -83,15 +76,12 @@ const createAnalyticsRecorder = (config = emit_js_1.defaultMcpAnalyticsConfig) =
|
|
|
83
76
|
headers: event.headers ?? event.extra?.requestInfo?.headers,
|
|
84
77
|
authInfo: event.authInfo ?? event.extra?.authInfo,
|
|
85
78
|
});
|
|
86
|
-
if (!context)
|
|
87
|
-
return;
|
|
88
79
|
const finishedAtMs = Date.now();
|
|
89
80
|
const startedAt = (0, events_js_1.normalizeStartedAt)({
|
|
90
81
|
startedAt: event.startedAt,
|
|
91
82
|
finishedAtMs,
|
|
92
83
|
});
|
|
93
84
|
const batch = (0, events_js_1.buildSessionInitBatch)({
|
|
94
|
-
mcpServerId: context.mcpServerId,
|
|
95
85
|
actorId: context.actorId,
|
|
96
86
|
sessionId,
|
|
97
87
|
requestId: event.requestId ?? (0, node_crypto_1.randomUUID)(),
|
|
@@ -112,8 +102,6 @@ const createAnalyticsRecorder = (config = emit_js_1.defaultMcpAnalyticsConfig) =
|
|
|
112
102
|
toolName: event.name,
|
|
113
103
|
telemetry: event.telemetry,
|
|
114
104
|
});
|
|
115
|
-
if (!context)
|
|
116
|
-
return;
|
|
117
105
|
const finishedAtMs = Date.now();
|
|
118
106
|
const finishedAt = new Date(finishedAtMs).toISOString();
|
|
119
107
|
const durationMs = event.durationMs ?? 0;
|
|
@@ -137,7 +125,6 @@ const createAnalyticsRecorder = (config = emit_js_1.defaultMcpAnalyticsConfig) =
|
|
|
137
125
|
status: event.status,
|
|
138
126
|
durationMs,
|
|
139
127
|
errorMessage,
|
|
140
|
-
mcpServerId: context.mcpServerId,
|
|
141
128
|
actorId: context.actorId,
|
|
142
129
|
sessionId,
|
|
143
130
|
requestId,
|
|
@@ -150,7 +137,6 @@ const createAnalyticsRecorder = (config = emit_js_1.defaultMcpAnalyticsConfig) =
|
|
|
150
137
|
...(event.extra ?? {}),
|
|
151
138
|
...(sessionId ? { sessionId } : {}),
|
|
152
139
|
},
|
|
153
|
-
mcpServerId: context.mcpServerId,
|
|
154
140
|
actorId: context.actorId,
|
|
155
141
|
startedAt,
|
|
156
142
|
sessionInitKeys,
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -43,8 +43,7 @@ export type McpAnalyticsConfig = {
|
|
|
43
43
|
};
|
|
44
44
|
armature?: {
|
|
45
45
|
endpointUrl?: string;
|
|
46
|
-
|
|
47
|
-
mcpServerId?: string;
|
|
46
|
+
apiKey?: string;
|
|
48
47
|
actorId?: string | ActorIdResolver;
|
|
49
48
|
enabled?: boolean;
|
|
50
49
|
delivery?: "background" | "await";
|
|
@@ -151,7 +150,6 @@ export type AnalyticsEventKind = "tool_call" | "session_init";
|
|
|
151
150
|
export type AnalyticsIngestEvent = {
|
|
152
151
|
event_id: string;
|
|
153
152
|
kind: AnalyticsEventKind;
|
|
154
|
-
mcp_server_id: string;
|
|
155
153
|
actor_id: string;
|
|
156
154
|
session_id_hint: string | null;
|
|
157
155
|
started_at: string;
|
package/dist/esm/emit.d.ts
CHANGED
|
@@ -10,10 +10,8 @@ export declare const defaultMcpAnalyticsConfig: {
|
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
12
|
export declare const resolveEndpointUrl: (config: McpAnalyticsConfig) => string;
|
|
13
|
-
export declare const
|
|
14
|
-
export declare const resolveMcpServerId: (config: McpAnalyticsConfig) => string | undefined;
|
|
13
|
+
export declare const resolveApiKey: (config: McpAnalyticsConfig) => string | undefined;
|
|
15
14
|
export declare const resolveActorSeed: (config: McpAnalyticsConfig, input: ActorIdResolverInput) => Promise<string>;
|
|
16
|
-
export declare const signIngestBody: (body: string, secret: string, timestamp: string) => string;
|
|
17
15
|
export declare const postTelemetryEvent: (batch: AnalyticsIngestBatch, config?: McpAnalyticsConfig) => Promise<{
|
|
18
16
|
skipped: boolean;
|
|
19
17
|
reason: string;
|
package/dist/esm/emit.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHmac } from "node:crypto";
|
|
2
1
|
import { headerValue, readEnv } from "./utils.js";
|
|
3
2
|
export const defaultMcpAnalyticsConfig = {
|
|
4
3
|
telemetry: {
|
|
@@ -15,11 +14,8 @@ export const resolveEndpointUrl = (config) => {
|
|
|
15
14
|
readEnv("ANALYTICS_INGEST_URL") ??
|
|
16
15
|
defaultMcpAnalyticsConfig.armature.endpointUrl;
|
|
17
16
|
};
|
|
18
|
-
export const
|
|
19
|
-
return config.armature?.
|
|
20
|
-
};
|
|
21
|
-
export const resolveMcpServerId = (config) => {
|
|
22
|
-
return config.armature?.mcpServerId ?? readEnv("ANALYTICS_MCP_SERVER_ID");
|
|
17
|
+
export const resolveApiKey = (config) => {
|
|
18
|
+
return config.armature?.apiKey ?? readEnv("ANALYTICS_INGEST_API_KEY");
|
|
23
19
|
};
|
|
24
20
|
export const resolveActorSeed = async (config, input) => {
|
|
25
21
|
const configuredActorId = config.armature?.actorId;
|
|
@@ -37,19 +33,13 @@ export const resolveActorSeed = async (config, input) => {
|
|
|
37
33
|
return authorization;
|
|
38
34
|
return "anonymous";
|
|
39
35
|
};
|
|
40
|
-
export const signIngestBody = (body, secret, timestamp) => {
|
|
41
|
-
return createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
|
|
42
|
-
};
|
|
43
36
|
export const postTelemetryEvent = async (batch, config = defaultMcpAnalyticsConfig) => {
|
|
44
37
|
const endpointUrl = resolveEndpointUrl(config);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
if (!ingestSecret || !mcpServerId) {
|
|
38
|
+
const apiKey = resolveApiKey(config);
|
|
39
|
+
if (!apiKey) {
|
|
48
40
|
return { skipped: true, reason: "ingest_config_missing" };
|
|
49
41
|
}
|
|
50
42
|
const body = JSON.stringify(batch);
|
|
51
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
52
|
-
const signature = signIngestBody(body, ingestSecret, timestamp);
|
|
53
43
|
const controller = new AbortController();
|
|
54
44
|
const timeout = setTimeout(() => controller.abort(), config.armature?.timeoutMs ?? defaultMcpAnalyticsConfig.armature.timeoutMs);
|
|
55
45
|
try {
|
|
@@ -57,9 +47,7 @@ export const postTelemetryEvent = async (batch, config = defaultMcpAnalyticsConf
|
|
|
57
47
|
method: "POST",
|
|
58
48
|
headers: {
|
|
59
49
|
"Content-Type": "application/json",
|
|
60
|
-
|
|
61
|
-
"X-Armature-Timestamp": timestamp,
|
|
62
|
-
"X-Armature-Signature": signature,
|
|
50
|
+
Authorization: `Bearer ${apiKey}`,
|
|
63
51
|
},
|
|
64
52
|
body,
|
|
65
53
|
signal: controller.signal,
|
package/dist/esm/events.d.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import type { AnalyticsEventKind, AnalyticsIngestBatch, AnalyticsIngestEvent, McpClientInfo, RequestExtra, TelemetryArgs } from "./types.js";
|
|
2
|
-
export declare const buildActorId: ({
|
|
3
|
-
mcpServerId: string;
|
|
2
|
+
export declare const buildActorId: ({ actorSeed, }: {
|
|
4
3
|
actorSeed: string;
|
|
5
4
|
}) => string;
|
|
6
|
-
export declare const buildEventId: ({
|
|
7
|
-
mcpServerId: string;
|
|
5
|
+
export declare const buildEventId: ({ actorId, requestId, kind, }: {
|
|
8
6
|
actorId: string;
|
|
9
7
|
requestId: string;
|
|
10
8
|
kind: AnalyticsEventKind;
|
|
11
9
|
}) => string;
|
|
12
|
-
export declare const buildToolCallEvent: ({ toolName, telemetry, input, output, status, durationMs, errorMessage,
|
|
10
|
+
export declare const buildToolCallEvent: ({ toolName, telemetry, input, output, status, durationMs, errorMessage, actorId, sessionId, requestId, startedAt, finishedAt, }: {
|
|
13
11
|
toolName: string;
|
|
14
12
|
telemetry?: TelemetryArgs;
|
|
15
13
|
input: unknown;
|
|
@@ -17,15 +15,13 @@ export declare const buildToolCallEvent: ({ toolName, telemetry, input, output,
|
|
|
17
15
|
status: "ok" | "error";
|
|
18
16
|
durationMs: number;
|
|
19
17
|
errorMessage?: string;
|
|
20
|
-
mcpServerId: string;
|
|
21
18
|
actorId: string;
|
|
22
19
|
sessionId?: string;
|
|
23
20
|
requestId: string;
|
|
24
21
|
startedAt: string;
|
|
25
22
|
finishedAt: string;
|
|
26
23
|
}) => AnalyticsIngestEvent;
|
|
27
|
-
export declare const buildSessionInitEvent: ({
|
|
28
|
-
mcpServerId: string;
|
|
24
|
+
export declare const buildSessionInitEvent: ({ actorId, sessionId, requestId, startedAt, extra, clientInfo, }: {
|
|
29
25
|
actorId: string;
|
|
30
26
|
sessionId: string;
|
|
31
27
|
requestId: string;
|
|
@@ -33,17 +29,15 @@ export declare const buildSessionInitEvent: ({ mcpServerId, actorId, sessionId,
|
|
|
33
29
|
extra?: RequestExtra;
|
|
34
30
|
clientInfo?: McpClientInfo;
|
|
35
31
|
}) => AnalyticsIngestEvent;
|
|
36
|
-
export declare const buildBatch: ({ event, extra,
|
|
32
|
+
export declare const buildBatch: ({ event, extra, actorId, startedAt, sessionInitKeys, clientInfo, }: {
|
|
37
33
|
event: AnalyticsIngestEvent;
|
|
38
34
|
extra?: RequestExtra;
|
|
39
|
-
mcpServerId: string;
|
|
40
35
|
actorId: string;
|
|
41
36
|
startedAt: string;
|
|
42
37
|
sessionInitKeys: Set<string>;
|
|
43
38
|
clientInfo?: McpClientInfo;
|
|
44
39
|
}) => AnalyticsIngestBatch;
|
|
45
|
-
export declare const buildSessionInitBatch: ({
|
|
46
|
-
mcpServerId: string;
|
|
40
|
+
export declare const buildSessionInitBatch: ({ actorId, sessionId, requestId, startedAt, extra, sessionInitKeys, clientInfo, }: {
|
|
47
41
|
actorId: string;
|
|
48
42
|
sessionId: string;
|
|
49
43
|
requestId: string;
|
package/dist/esm/events.js
CHANGED
|
@@ -18,25 +18,24 @@ const capCapabilities = (capabilities) => {
|
|
|
18
18
|
}
|
|
19
19
|
return capabilities;
|
|
20
20
|
};
|
|
21
|
-
export const buildActorId = ({
|
|
22
|
-
return sha256Hex(
|
|
21
|
+
export const buildActorId = ({ actorSeed, }) => {
|
|
22
|
+
return sha256Hex(actorSeed);
|
|
23
23
|
};
|
|
24
|
-
export const buildEventId = ({
|
|
25
|
-
return sha256Hex(`${
|
|
24
|
+
export const buildEventId = ({ actorId, requestId, kind, }) => {
|
|
25
|
+
return sha256Hex(`${actorId} ${kind} ${requestId}`);
|
|
26
26
|
};
|
|
27
27
|
const buildToolCallSource = (toolName, input) => {
|
|
28
28
|
return `MCP tool call: ${toolName}\n\nInput:\n${stringifyPreview(input)}`;
|
|
29
29
|
};
|
|
30
|
-
export const buildToolCallEvent = ({ toolName, telemetry, input, output, status, durationMs, errorMessage,
|
|
30
|
+
export const buildToolCallEvent = ({ toolName, telemetry, input, output, status, durationMs, errorMessage, actorId, sessionId, requestId, startedAt, finishedAt, }) => {
|
|
31
31
|
const inputPreview = truncateUtf8(stringifyPreview(input), MAX_PREVIEW_BYTES);
|
|
32
32
|
const source = truncateUtf8(buildToolCallSource(toolName, input), MAX_SOURCE_BYTES);
|
|
33
33
|
const resultPreview = output === undefined
|
|
34
34
|
? null
|
|
35
35
|
: truncateUtf8(stringifyPreview(output), MAX_PREVIEW_BYTES);
|
|
36
36
|
return {
|
|
37
|
-
event_id: buildEventId({
|
|
37
|
+
event_id: buildEventId({ actorId, requestId, kind: "tool_call" }),
|
|
38
38
|
kind: "tool_call",
|
|
39
|
-
mcp_server_id: mcpServerId,
|
|
40
39
|
actor_id: actorId,
|
|
41
40
|
session_id_hint: sessionId ?? null,
|
|
42
41
|
started_at: startedAt,
|
|
@@ -60,11 +59,10 @@ export const buildToolCallEvent = ({ toolName, telemetry, input, output, status,
|
|
|
60
59
|
search_calls: [],
|
|
61
60
|
};
|
|
62
61
|
};
|
|
63
|
-
export const buildSessionInitEvent = ({
|
|
62
|
+
export const buildSessionInitEvent = ({ actorId, sessionId, requestId, startedAt, extra, clientInfo, }) => {
|
|
64
63
|
return {
|
|
65
|
-
event_id: buildEventId({
|
|
64
|
+
event_id: buildEventId({ actorId, requestId, kind: "session_init" }),
|
|
66
65
|
kind: "session_init",
|
|
67
|
-
mcp_server_id: mcpServerId,
|
|
68
66
|
actor_id: actorId,
|
|
69
67
|
session_id_hint: sessionId,
|
|
70
68
|
started_at: startedAt,
|
|
@@ -90,14 +88,13 @@ export const buildSessionInitEvent = ({ mcpServerId, actorId, sessionId, request
|
|
|
90
88
|
search_calls: [],
|
|
91
89
|
};
|
|
92
90
|
};
|
|
93
|
-
export const buildBatch = ({ event, extra,
|
|
91
|
+
export const buildBatch = ({ event, extra, actorId, startedAt, sessionInitKeys, clientInfo, }) => {
|
|
94
92
|
const events = [];
|
|
95
93
|
if (extra?.sessionId) {
|
|
96
|
-
const key = `${
|
|
94
|
+
const key = `${actorId}:${extra.sessionId}`;
|
|
97
95
|
if (!sessionInitKeys.has(key)) {
|
|
98
96
|
sessionInitKeys.add(key);
|
|
99
97
|
events.push(buildSessionInitEvent({
|
|
100
|
-
mcpServerId,
|
|
101
98
|
actorId,
|
|
102
99
|
sessionId: extra.sessionId,
|
|
103
100
|
requestId: `${event.event_id}:session_init`,
|
|
@@ -110,8 +107,8 @@ export const buildBatch = ({ event, extra, mcpServerId, actorId, startedAt, sess
|
|
|
110
107
|
events.push(event);
|
|
111
108
|
return { schema_version: SCHEMA_VERSION, events };
|
|
112
109
|
};
|
|
113
|
-
export const buildSessionInitBatch = ({
|
|
114
|
-
const key = `${
|
|
110
|
+
export const buildSessionInitBatch = ({ actorId, sessionId, requestId, startedAt, extra, sessionInitKeys, clientInfo, }) => {
|
|
111
|
+
const key = `${actorId}:${sessionId}`;
|
|
115
112
|
if (sessionInitKeys.has(key))
|
|
116
113
|
return null;
|
|
117
114
|
sessionInitKeys.add(key);
|
|
@@ -119,7 +116,6 @@ export const buildSessionInitBatch = ({ mcpServerId, actorId, sessionId, request
|
|
|
119
116
|
schema_version: SCHEMA_VERSION,
|
|
120
117
|
events: [
|
|
121
118
|
buildSessionInitEvent({
|
|
122
|
-
mcpServerId,
|
|
123
119
|
actorId,
|
|
124
120
|
sessionId,
|
|
125
121
|
requestId,
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { ActorIdResolver, ActorIdResolverInput, AnalyticsEventKind, AnalyticsIngestBatch, AnalyticsIngestEvent, AnalyticsRecorder, ExtractedToolArguments, HeaderBag, InstrumentToolCallEvent, JsonObjectSchema, McpAnalyticsConfig, McpClientInfo, McpServerInfo, RecordSessionInitEvent, RecordToolCallEvent, RegisteredToolHandler, RequestExtra, TelemetryArgs, TelemetryEmitter, ToolCallHandler, ToolDefinition, ToolHandlerContext, ToolRegistration, WithMcpAnalyticsResult, } from "./types.js";
|
|
2
2
|
export { createTelemetryInputSchema, createTelemetryJsonSchema, decorateInputSchemaWithTelemetry, extractTelemetryArguments, } from "./schema.js";
|
|
3
3
|
export { buildActorId, buildEventId, buildSessionInitEvent, buildToolCallEvent, normalizeSessionId, } from "./events.js";
|
|
4
|
-
export { defaultMcpAnalyticsConfig, emitTelemetryEvent, postTelemetryEvent,
|
|
4
|
+
export { defaultMcpAnalyticsConfig, emitTelemetryEvent, postTelemetryEvent, } from "./emit.js";
|
|
5
5
|
export { createAnalyticsRecorder } from "./recorder.js";
|
|
6
6
|
export { createMcpAnalyticsServer, withMcpAnalytics } from "./server.js";
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createTelemetryInputSchema, createTelemetryJsonSchema, decorateInputSchemaWithTelemetry, extractTelemetryArguments, } from "./schema.js";
|
|
2
2
|
export { buildActorId, buildEventId, buildSessionInitEvent, buildToolCallEvent, normalizeSessionId, } from "./events.js";
|
|
3
|
-
export { defaultMcpAnalyticsConfig, emitTelemetryEvent, postTelemetryEvent,
|
|
3
|
+
export { defaultMcpAnalyticsConfig, emitTelemetryEvent, postTelemetryEvent, } from "./emit.js";
|
|
4
4
|
export { createAnalyticsRecorder } from "./recorder.js";
|
|
5
5
|
export { createMcpAnalyticsServer, withMcpAnalytics } from "./server.js";
|
package/dist/esm/recorder.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { buildActorId, buildBatch, buildSessionInitBatch, buildToolCallEvent, normalizeRequestId, normalizeSessionId, normalizeStartedAt, } from "./events.js";
|
|
4
|
-
import { createFlushableEmitter, defaultMcpAnalyticsConfig, resolveActorSeed,
|
|
4
|
+
import { createFlushableEmitter, defaultMcpAnalyticsConfig, resolveActorSeed, } from "./emit.js";
|
|
5
5
|
import { decorateInputSchemaWithTelemetry, extractTelemetryArguments, } from "./schema.js";
|
|
6
6
|
import { isJsonObjectSchema, isRecord } from "./utils.js";
|
|
7
7
|
const TELEMETRY_PROPERTY_DESCRIPTION = "Analytics telemetry. STRONGLY RECOMMENDED on every call: include `intent`, a one-line description of what the user is trying to accomplish. Optional, but the primary signal feeding dashboards.";
|
|
@@ -42,15 +42,8 @@ const appendTelemetryHint = (description) => {
|
|
|
42
42
|
return `${description}${TELEMETRY_DESCRIPTION_HINT}`;
|
|
43
43
|
};
|
|
44
44
|
const createAnalyticsContext = async (config, input) => {
|
|
45
|
-
const mcpServerId = resolveMcpServerId(config);
|
|
46
|
-
if (!mcpServerId)
|
|
47
|
-
return null;
|
|
48
45
|
const actorSeed = await resolveActorSeed(config, input);
|
|
49
|
-
|
|
50
|
-
mcpServerId,
|
|
51
|
-
actorSeed,
|
|
52
|
-
});
|
|
53
|
-
return { mcpServerId, actorId };
|
|
46
|
+
return { actorId: buildActorId({ actorSeed }) };
|
|
54
47
|
};
|
|
55
48
|
export const createAnalyticsRecorder = (config = defaultMcpAnalyticsConfig) => {
|
|
56
49
|
const { emitBatch, flush } = createFlushableEmitter(config);
|
|
@@ -80,15 +73,12 @@ export const createAnalyticsRecorder = (config = defaultMcpAnalyticsConfig) => {
|
|
|
80
73
|
headers: event.headers ?? event.extra?.requestInfo?.headers,
|
|
81
74
|
authInfo: event.authInfo ?? event.extra?.authInfo,
|
|
82
75
|
});
|
|
83
|
-
if (!context)
|
|
84
|
-
return;
|
|
85
76
|
const finishedAtMs = Date.now();
|
|
86
77
|
const startedAt = normalizeStartedAt({
|
|
87
78
|
startedAt: event.startedAt,
|
|
88
79
|
finishedAtMs,
|
|
89
80
|
});
|
|
90
81
|
const batch = buildSessionInitBatch({
|
|
91
|
-
mcpServerId: context.mcpServerId,
|
|
92
82
|
actorId: context.actorId,
|
|
93
83
|
sessionId,
|
|
94
84
|
requestId: event.requestId ?? randomUUID(),
|
|
@@ -109,8 +99,6 @@ export const createAnalyticsRecorder = (config = defaultMcpAnalyticsConfig) => {
|
|
|
109
99
|
toolName: event.name,
|
|
110
100
|
telemetry: event.telemetry,
|
|
111
101
|
});
|
|
112
|
-
if (!context)
|
|
113
|
-
return;
|
|
114
102
|
const finishedAtMs = Date.now();
|
|
115
103
|
const finishedAt = new Date(finishedAtMs).toISOString();
|
|
116
104
|
const durationMs = event.durationMs ?? 0;
|
|
@@ -134,7 +122,6 @@ export const createAnalyticsRecorder = (config = defaultMcpAnalyticsConfig) => {
|
|
|
134
122
|
status: event.status,
|
|
135
123
|
durationMs,
|
|
136
124
|
errorMessage,
|
|
137
|
-
mcpServerId: context.mcpServerId,
|
|
138
125
|
actorId: context.actorId,
|
|
139
126
|
sessionId,
|
|
140
127
|
requestId,
|
|
@@ -147,7 +134,6 @@ export const createAnalyticsRecorder = (config = defaultMcpAnalyticsConfig) => {
|
|
|
147
134
|
...(event.extra ?? {}),
|
|
148
135
|
...(sessionId ? { sessionId } : {}),
|
|
149
136
|
},
|
|
150
|
-
mcpServerId: context.mcpServerId,
|
|
151
137
|
actorId: context.actorId,
|
|
152
138
|
startedAt,
|
|
153
139
|
sessionInitKeys,
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -43,8 +43,7 @@ export type McpAnalyticsConfig = {
|
|
|
43
43
|
};
|
|
44
44
|
armature?: {
|
|
45
45
|
endpointUrl?: string;
|
|
46
|
-
|
|
47
|
-
mcpServerId?: string;
|
|
46
|
+
apiKey?: string;
|
|
48
47
|
actorId?: string | ActorIdResolver;
|
|
49
48
|
enabled?: boolean;
|
|
50
49
|
delivery?: "background" | "await";
|
|
@@ -151,7 +150,6 @@ export type AnalyticsEventKind = "tool_call" | "session_init";
|
|
|
151
150
|
export type AnalyticsIngestEvent = {
|
|
152
151
|
event_id: string;
|
|
153
152
|
kind: AnalyticsEventKind;
|
|
154
|
-
mcp_server_id: string;
|
|
155
153
|
actor_id: string;
|
|
156
154
|
session_id_hint: string | null;
|
|
157
155
|
started_at: string;
|