@armature-tech/mcp-analytics 0.2.5
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +283 -0
- package/SKILL.md +328 -0
- package/dist/cjs/emit.d.ts +32 -0
- package/dist/cjs/emit.js +157 -0
- package/dist/cjs/events.d.ts +61 -0
- package/dist/cjs/events.js +166 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/mastra.d.ts +24 -0
- package/dist/cjs/mastra.js +59 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/recorder.d.ts +2 -0
- package/dist/cjs/recorder.js +282 -0
- package/dist/cjs/schema.d.ts +64 -0
- package/dist/cjs/schema.js +101 -0
- package/dist/cjs/server.d.ts +3 -0
- package/dist/cjs/server.js +80 -0
- package/dist/cjs/types.d.ts +179 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils.d.ts +16 -0
- package/dist/cjs/utils.js +72 -0
- package/dist/esm/emit.d.ts +32 -0
- package/dist/esm/emit.js +146 -0
- package/dist/esm/events.d.ts +61 -0
- package/dist/esm/events.js +154 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/mastra.d.ts +24 -0
- package/dist/esm/mastra.js +53 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/recorder.d.ts +2 -0
- package/dist/esm/recorder.js +278 -0
- package/dist/esm/schema.d.ts +64 -0
- package/dist/esm/schema.js +94 -0
- package/dist/esm/server.d.ts +3 -0
- package/dist/esm/server.js +75 -0
- package/dist/esm/types.d.ts +179 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.d.ts +16 -0
- package/dist/esm/utils.js +61 -0
- package/package.json +87 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeStartedAt = exports.normalizeRequestId = exports.normalizeSessionId = exports.buildSessionInitBatch = exports.buildBatch = exports.buildSessionInitEvent = exports.buildToolCallEvent = exports.buildEventId = exports.buildActorId = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const utils_js_1 = require("./utils.js");
|
|
6
|
+
const trimOrUndefined = (value) => {
|
|
7
|
+
if (typeof value !== "string")
|
|
8
|
+
return undefined;
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
11
|
+
};
|
|
12
|
+
const capCapabilities = (capabilities) => {
|
|
13
|
+
if (!(0, utils_js_1.isRecord)(capabilities))
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
if (JSON.stringify(capabilities).length > utils_js_1.MAX_CAPABILITIES_BYTES)
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return capabilities;
|
|
23
|
+
};
|
|
24
|
+
const buildActorId = ({ mcpServerId, actorSeed, }) => {
|
|
25
|
+
return (0, utils_js_1.sha256Hex)(`${mcpServerId} ${actorSeed}`);
|
|
26
|
+
};
|
|
27
|
+
exports.buildActorId = buildActorId;
|
|
28
|
+
const buildEventId = ({ mcpServerId, actorId, requestId, kind, }) => {
|
|
29
|
+
return (0, utils_js_1.sha256Hex)(`${mcpServerId} ${actorId} ${kind} ${requestId}`);
|
|
30
|
+
};
|
|
31
|
+
exports.buildEventId = buildEventId;
|
|
32
|
+
const buildToolCallSource = (toolName, input) => {
|
|
33
|
+
return `MCP tool call: ${toolName}\n\nInput:\n${(0, utils_js_1.stringifyPreview)(input)}`;
|
|
34
|
+
};
|
|
35
|
+
const buildToolCallEvent = ({ toolName, telemetry, input, output, status, durationMs, errorMessage, mcpServerId, actorId, sessionId, requestId, startedAt, finishedAt, }) => {
|
|
36
|
+
const inputPreview = (0, utils_js_1.truncateUtf8)((0, utils_js_1.stringifyPreview)(input), utils_js_1.MAX_PREVIEW_BYTES);
|
|
37
|
+
const source = (0, utils_js_1.truncateUtf8)(buildToolCallSource(toolName, input), utils_js_1.MAX_SOURCE_BYTES);
|
|
38
|
+
const resultPreview = output === undefined
|
|
39
|
+
? null
|
|
40
|
+
: (0, utils_js_1.truncateUtf8)((0, utils_js_1.stringifyPreview)(output), utils_js_1.MAX_PREVIEW_BYTES);
|
|
41
|
+
return {
|
|
42
|
+
event_id: (0, exports.buildEventId)({ mcpServerId, actorId, requestId, kind: "tool_call" }),
|
|
43
|
+
kind: "tool_call",
|
|
44
|
+
mcp_server_id: mcpServerId,
|
|
45
|
+
actor_id: actorId,
|
|
46
|
+
session_id_hint: sessionId ?? null,
|
|
47
|
+
started_at: startedAt,
|
|
48
|
+
finished_at: finishedAt,
|
|
49
|
+
duration_ms: durationMs,
|
|
50
|
+
ok: status === "ok",
|
|
51
|
+
error: errorMessage ?? null,
|
|
52
|
+
metadata: {
|
|
53
|
+
tool_name: toolName,
|
|
54
|
+
intent: telemetry?.intent ?? null,
|
|
55
|
+
context: telemetry?.context ?? null,
|
|
56
|
+
frustration_level: telemetry?.frustration_level ?? null,
|
|
57
|
+
input_preview: inputPreview.value,
|
|
58
|
+
},
|
|
59
|
+
script_source: source.value,
|
|
60
|
+
script_source_truncated: source.truncated,
|
|
61
|
+
result_preview: resultPreview?.value ?? null,
|
|
62
|
+
result_truncated: resultPreview?.truncated ?? false,
|
|
63
|
+
calls: [],
|
|
64
|
+
logs: [],
|
|
65
|
+
search_calls: [],
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
exports.buildToolCallEvent = buildToolCallEvent;
|
|
69
|
+
const buildSessionInitEvent = ({ mcpServerId, actorId, sessionId, requestId, startedAt, extra, clientInfo, }) => {
|
|
70
|
+
return {
|
|
71
|
+
event_id: (0, exports.buildEventId)({ mcpServerId, actorId, requestId, kind: "session_init" }),
|
|
72
|
+
kind: "session_init",
|
|
73
|
+
mcp_server_id: mcpServerId,
|
|
74
|
+
actor_id: actorId,
|
|
75
|
+
session_id_hint: sessionId,
|
|
76
|
+
started_at: startedAt,
|
|
77
|
+
finished_at: startedAt,
|
|
78
|
+
duration_ms: 0,
|
|
79
|
+
ok: true,
|
|
80
|
+
error: null,
|
|
81
|
+
metadata: {
|
|
82
|
+
client_name: trimOrUndefined(clientInfo?.name)
|
|
83
|
+
?? trimOrUndefined(extra?.authInfo?.clientId)
|
|
84
|
+
?? null,
|
|
85
|
+
client_version: trimOrUndefined(clientInfo?.version) ?? null,
|
|
86
|
+
protocol_version: trimOrUndefined(clientInfo?.protocolVersion) ?? null,
|
|
87
|
+
capabilities: capCapabilities(clientInfo?.capabilities),
|
|
88
|
+
user_agent: (0, utils_js_1.headerValue)(extra?.requestInfo?.headers, "user-agent"),
|
|
89
|
+
},
|
|
90
|
+
script_source: null,
|
|
91
|
+
script_source_truncated: false,
|
|
92
|
+
result_preview: null,
|
|
93
|
+
result_truncated: false,
|
|
94
|
+
calls: [],
|
|
95
|
+
logs: [],
|
|
96
|
+
search_calls: [],
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
exports.buildSessionInitEvent = buildSessionInitEvent;
|
|
100
|
+
const buildBatch = ({ event, extra, mcpServerId, actorId, startedAt, sessionInitKeys, clientInfo, }) => {
|
|
101
|
+
const events = [];
|
|
102
|
+
if (extra?.sessionId) {
|
|
103
|
+
const key = `${mcpServerId}:${actorId}:${extra.sessionId}`;
|
|
104
|
+
if (!sessionInitKeys.has(key)) {
|
|
105
|
+
sessionInitKeys.add(key);
|
|
106
|
+
events.push((0, exports.buildSessionInitEvent)({
|
|
107
|
+
mcpServerId,
|
|
108
|
+
actorId,
|
|
109
|
+
sessionId: extra.sessionId,
|
|
110
|
+
requestId: `${event.event_id}:session_init`,
|
|
111
|
+
startedAt,
|
|
112
|
+
extra,
|
|
113
|
+
clientInfo,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
events.push(event);
|
|
118
|
+
return { schema_version: utils_js_1.SCHEMA_VERSION, events };
|
|
119
|
+
};
|
|
120
|
+
exports.buildBatch = buildBatch;
|
|
121
|
+
const buildSessionInitBatch = ({ mcpServerId, actorId, sessionId, requestId, startedAt, extra, sessionInitKeys, clientInfo, }) => {
|
|
122
|
+
const key = `${mcpServerId}:${actorId}:${sessionId}`;
|
|
123
|
+
if (sessionInitKeys.has(key))
|
|
124
|
+
return null;
|
|
125
|
+
sessionInitKeys.add(key);
|
|
126
|
+
return {
|
|
127
|
+
schema_version: utils_js_1.SCHEMA_VERSION,
|
|
128
|
+
events: [
|
|
129
|
+
(0, exports.buildSessionInitEvent)({
|
|
130
|
+
mcpServerId,
|
|
131
|
+
actorId,
|
|
132
|
+
sessionId,
|
|
133
|
+
requestId,
|
|
134
|
+
startedAt,
|
|
135
|
+
extra,
|
|
136
|
+
clientInfo,
|
|
137
|
+
}),
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
exports.buildSessionInitBatch = buildSessionInitBatch;
|
|
142
|
+
const normalizeSessionId = (eventSessionId, extra) => {
|
|
143
|
+
const explicit = trimOrUndefined(eventSessionId) ?? trimOrUndefined(extra?.sessionId);
|
|
144
|
+
if (explicit)
|
|
145
|
+
return explicit;
|
|
146
|
+
return trimOrUndefined((0, utils_js_1.headerValue)(extra?.requestInfo?.headers, "mcp-session-id"));
|
|
147
|
+
};
|
|
148
|
+
exports.normalizeSessionId = normalizeSessionId;
|
|
149
|
+
const normalizeRequestId = (eventRequestId, extra) => {
|
|
150
|
+
return eventRequestId ?? (extra?.requestId === undefined ? (0, node_crypto_1.randomUUID)() : String(extra.requestId));
|
|
151
|
+
};
|
|
152
|
+
exports.normalizeRequestId = normalizeRequestId;
|
|
153
|
+
const normalizeStartedAt = ({ startedAt, durationMs, finishedAtMs, }) => {
|
|
154
|
+
if (startedAt instanceof Date)
|
|
155
|
+
return startedAt.toISOString();
|
|
156
|
+
if (typeof startedAt === "string")
|
|
157
|
+
return new Date(startedAt).toISOString();
|
|
158
|
+
if (typeof startedAt === "number" && startedAt > 1_000_000_000_000) {
|
|
159
|
+
return new Date(startedAt).toISOString();
|
|
160
|
+
}
|
|
161
|
+
if (durationMs !== undefined) {
|
|
162
|
+
return new Date(finishedAtMs - durationMs).toISOString();
|
|
163
|
+
}
|
|
164
|
+
return new Date(finishedAtMs).toISOString();
|
|
165
|
+
};
|
|
166
|
+
exports.normalizeStartedAt = normalizeStartedAt;
|
|
@@ -0,0 +1,6 @@
|
|
|
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
|
+
export { createTelemetryInputSchema, createTelemetryJsonSchema, decorateInputSchemaWithTelemetry, extractTelemetryArguments, } from "./schema.js";
|
|
3
|
+
export { buildActorId, buildEventId, buildSessionInitEvent, buildToolCallEvent, normalizeSessionId, } from "./events.js";
|
|
4
|
+
export { defaultMcpAnalyticsConfig, emitTelemetryEvent, postTelemetryEvent, signIngestBody, } from "./emit.js";
|
|
5
|
+
export { createAnalyticsRecorder } from "./recorder.js";
|
|
6
|
+
export { createMcpAnalyticsServer, withMcpAnalytics } from "./server.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withMcpAnalytics = exports.createMcpAnalyticsServer = exports.createAnalyticsRecorder = exports.signIngestBody = 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
|
+
var schema_js_1 = require("./schema.js");
|
|
5
|
+
Object.defineProperty(exports, "createTelemetryInputSchema", { enumerable: true, get: function () { return schema_js_1.createTelemetryInputSchema; } });
|
|
6
|
+
Object.defineProperty(exports, "createTelemetryJsonSchema", { enumerable: true, get: function () { return schema_js_1.createTelemetryJsonSchema; } });
|
|
7
|
+
Object.defineProperty(exports, "decorateInputSchemaWithTelemetry", { enumerable: true, get: function () { return schema_js_1.decorateInputSchemaWithTelemetry; } });
|
|
8
|
+
Object.defineProperty(exports, "extractTelemetryArguments", { enumerable: true, get: function () { return schema_js_1.extractTelemetryArguments; } });
|
|
9
|
+
var events_js_1 = require("./events.js");
|
|
10
|
+
Object.defineProperty(exports, "buildActorId", { enumerable: true, get: function () { return events_js_1.buildActorId; } });
|
|
11
|
+
Object.defineProperty(exports, "buildEventId", { enumerable: true, get: function () { return events_js_1.buildEventId; } });
|
|
12
|
+
Object.defineProperty(exports, "buildSessionInitEvent", { enumerable: true, get: function () { return events_js_1.buildSessionInitEvent; } });
|
|
13
|
+
Object.defineProperty(exports, "buildToolCallEvent", { enumerable: true, get: function () { return events_js_1.buildToolCallEvent; } });
|
|
14
|
+
Object.defineProperty(exports, "normalizeSessionId", { enumerable: true, get: function () { return events_js_1.normalizeSessionId; } });
|
|
15
|
+
var emit_js_1 = require("./emit.js");
|
|
16
|
+
Object.defineProperty(exports, "defaultMcpAnalyticsConfig", { enumerable: true, get: function () { return emit_js_1.defaultMcpAnalyticsConfig; } });
|
|
17
|
+
Object.defineProperty(exports, "emitTelemetryEvent", { enumerable: true, get: function () { return emit_js_1.emitTelemetryEvent; } });
|
|
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
|
+
var recorder_js_1 = require("./recorder.js");
|
|
21
|
+
Object.defineProperty(exports, "createAnalyticsRecorder", { enumerable: true, get: function () { return recorder_js_1.createAnalyticsRecorder; } });
|
|
22
|
+
var server_js_1 = require("./server.js");
|
|
23
|
+
Object.defineProperty(exports, "createMcpAnalyticsServer", { enumerable: true, get: function () { return server_js_1.createMcpAnalyticsServer; } });
|
|
24
|
+
Object.defineProperty(exports, "withMcpAnalytics", { enumerable: true, get: function () { return server_js_1.withMcpAnalytics; } });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AnalyticsRecorder, McpAnalyticsConfig, RequestExtra } from "./types.js";
|
|
2
|
+
export type MastraToolExecute = (inputData: unknown, context?: unknown) => unknown | Promise<unknown>;
|
|
3
|
+
export type MastraTool = {
|
|
4
|
+
id?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
inputSchema?: unknown;
|
|
7
|
+
outputSchema?: unknown;
|
|
8
|
+
execute?: MastraToolExecute;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
export type MastraToolMap = Record<string, MastraTool>;
|
|
12
|
+
export type MastraAdapterOptions = McpAnalyticsConfig & {
|
|
13
|
+
resolveExtra?: (mastraContext: unknown) => RequestExtra | undefined;
|
|
14
|
+
};
|
|
15
|
+
export declare const wrapMastraToolsWithRecorder: (tools: MastraToolMap, recorder: AnalyticsRecorder, config?: McpAnalyticsConfig, options?: {
|
|
16
|
+
resolveExtra?: MastraAdapterOptions["resolveExtra"];
|
|
17
|
+
}) => MastraToolMap;
|
|
18
|
+
export declare const wrapMastraTools: (tools: MastraToolMap, options?: MastraAdapterOptions) => MastraToolMap;
|
|
19
|
+
export type MastraAnalytics = {
|
|
20
|
+
recorder: AnalyticsRecorder;
|
|
21
|
+
wrapTools: (tools: MastraToolMap) => MastraToolMap;
|
|
22
|
+
flush: () => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
export declare const createMastraAnalytics: (options?: MastraAdapterOptions) => MastraAnalytics;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMastraAnalytics = exports.wrapMastraTools = exports.wrapMastraToolsWithRecorder = void 0;
|
|
4
|
+
const recorder_js_1 = require("./recorder.js");
|
|
5
|
+
const schema_js_1 = require("./schema.js");
|
|
6
|
+
const wrapOneTool = (toolKey, tool, recorder, config, resolveExtra) => {
|
|
7
|
+
if (typeof tool?.execute !== "function") {
|
|
8
|
+
return tool;
|
|
9
|
+
}
|
|
10
|
+
const originalExecute = tool.execute;
|
|
11
|
+
const toolName = tool.id ?? toolKey;
|
|
12
|
+
const decoratedInputSchema = tool.inputSchema === undefined
|
|
13
|
+
? undefined
|
|
14
|
+
: (0, schema_js_1.decorateInputSchemaWithTelemetry)(tool.inputSchema, config);
|
|
15
|
+
const wrappedExecute = (inputData, mastraContext) => {
|
|
16
|
+
const extra = resolveExtra?.(mastraContext);
|
|
17
|
+
return recorder.instrumentToolCall({
|
|
18
|
+
name: toolName,
|
|
19
|
+
args: inputData,
|
|
20
|
+
ctx: mastraContext,
|
|
21
|
+
extra,
|
|
22
|
+
sessionId: extra?.sessionId,
|
|
23
|
+
requestId: extra?.requestId === undefined ? undefined : String(extra.requestId),
|
|
24
|
+
authInfo: extra?.authInfo,
|
|
25
|
+
headers: extra?.requestInfo?.headers,
|
|
26
|
+
}, (strippedArgs) => originalExecute(strippedArgs, mastraContext));
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
...tool,
|
|
30
|
+
...(decoratedInputSchema !== undefined
|
|
31
|
+
? { inputSchema: decoratedInputSchema }
|
|
32
|
+
: {}),
|
|
33
|
+
execute: wrappedExecute,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const wrapMastraToolsWithRecorder = (tools, recorder, config = {}, options = {}) => {
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const [key, tool] of Object.entries(tools)) {
|
|
39
|
+
out[key] = wrapOneTool(key, tool, recorder, config, options.resolveExtra);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
};
|
|
43
|
+
exports.wrapMastraToolsWithRecorder = wrapMastraToolsWithRecorder;
|
|
44
|
+
const wrapMastraTools = (tools, options = {}) => {
|
|
45
|
+
const { resolveExtra, ...config } = options;
|
|
46
|
+
const recorder = (0, recorder_js_1.createAnalyticsRecorder)(config);
|
|
47
|
+
return (0, exports.wrapMastraToolsWithRecorder)(tools, recorder, config, { resolveExtra });
|
|
48
|
+
};
|
|
49
|
+
exports.wrapMastraTools = wrapMastraTools;
|
|
50
|
+
const createMastraAnalytics = (options = {}) => {
|
|
51
|
+
const { resolveExtra, ...config } = options;
|
|
52
|
+
const recorder = (0, recorder_js_1.createAnalyticsRecorder)(config);
|
|
53
|
+
return {
|
|
54
|
+
recorder,
|
|
55
|
+
wrapTools: (tools) => (0, exports.wrapMastraToolsWithRecorder)(tools, recorder, config, { resolveExtra }),
|
|
56
|
+
flush: recorder.flush,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
exports.createMastraAnalytics = createMastraAnalytics;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAnalyticsRecorder = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6
|
+
const events_js_1 = require("./events.js");
|
|
7
|
+
const emit_js_1 = require("./emit.js");
|
|
8
|
+
const schema_js_1 = require("./schema.js");
|
|
9
|
+
const utils_js_1 = require("./utils.js");
|
|
10
|
+
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.";
|
|
11
|
+
const TELEMETRY_INTENT_DESCRIPTION = "Provide a one-line user intent. Omit only if truly impossible.";
|
|
12
|
+
const TELEMETRY_DESCRIPTION_HINT = "\n\nPass telemetry.intent with a one-line user intent for analytics.";
|
|
13
|
+
const TELEMETRY_DESCRIPTION_HINT_MARKER = TELEMETRY_DESCRIPTION_HINT.trim();
|
|
14
|
+
const nudgeTelemetryDescriptions = (schema) => {
|
|
15
|
+
if (!(0, utils_js_1.isJsonObjectSchema)(schema))
|
|
16
|
+
return schema;
|
|
17
|
+
const telemetry = schema.properties?.telemetry;
|
|
18
|
+
if (!(0, utils_js_1.isJsonObjectSchema)(telemetry))
|
|
19
|
+
return schema;
|
|
20
|
+
const intent = telemetry.properties?.intent;
|
|
21
|
+
const nudgedTelemetry = {
|
|
22
|
+
...telemetry,
|
|
23
|
+
description: TELEMETRY_PROPERTY_DESCRIPTION,
|
|
24
|
+
properties: {
|
|
25
|
+
...(telemetry.properties ?? {}),
|
|
26
|
+
...((0, utils_js_1.isRecord)(intent)
|
|
27
|
+
? {
|
|
28
|
+
intent: { ...intent, description: TELEMETRY_INTENT_DESCRIPTION },
|
|
29
|
+
}
|
|
30
|
+
: {}),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
...schema,
|
|
35
|
+
properties: { ...schema.properties, telemetry: nudgedTelemetry },
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const appendTelemetryHint = (description) => {
|
|
39
|
+
if (description === undefined) {
|
|
40
|
+
return TELEMETRY_DESCRIPTION_HINT.trimStart();
|
|
41
|
+
}
|
|
42
|
+
if (description.includes(TELEMETRY_DESCRIPTION_HINT_MARKER)) {
|
|
43
|
+
return description;
|
|
44
|
+
}
|
|
45
|
+
return `${description}${TELEMETRY_DESCRIPTION_HINT}`;
|
|
46
|
+
};
|
|
47
|
+
const createAnalyticsContext = async (config, input) => {
|
|
48
|
+
const mcpServerId = (0, emit_js_1.resolveMcpServerId)(config);
|
|
49
|
+
if (!mcpServerId)
|
|
50
|
+
return null;
|
|
51
|
+
const actorSeed = await (0, emit_js_1.resolveActorSeed)(config, input);
|
|
52
|
+
const actorId = (0, events_js_1.buildActorId)({
|
|
53
|
+
mcpServerId,
|
|
54
|
+
actorSeed,
|
|
55
|
+
});
|
|
56
|
+
return { mcpServerId, actorId };
|
|
57
|
+
};
|
|
58
|
+
const createAnalyticsRecorder = (config = emit_js_1.defaultMcpAnalyticsConfig) => {
|
|
59
|
+
const { emitBatch, flush } = (0, emit_js_1.createFlushableEmitter)(config);
|
|
60
|
+
const sessionInitKeys = new Set();
|
|
61
|
+
const analyticsContextFor = async (input) => {
|
|
62
|
+
return createAnalyticsContext(config, input);
|
|
63
|
+
};
|
|
64
|
+
const decorateDefinitions = (defs) => {
|
|
65
|
+
return defs.map((definition) => {
|
|
66
|
+
const inputSchema = nudgeTelemetryDescriptions((0, schema_js_1.decorateInputSchemaWithTelemetry)(definition.inputSchema ?? { type: "object", properties: {} }, config));
|
|
67
|
+
return {
|
|
68
|
+
...definition,
|
|
69
|
+
description: appendTelemetryHint(typeof definition.description === "string"
|
|
70
|
+
? definition.description
|
|
71
|
+
: undefined),
|
|
72
|
+
inputSchema,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const recordSessionInit = async (event) => {
|
|
77
|
+
const sessionId = (0, events_js_1.normalizeSessionId)(event.sessionId, event.extra);
|
|
78
|
+
if (!sessionId)
|
|
79
|
+
return;
|
|
80
|
+
const context = await analyticsContextFor({
|
|
81
|
+
ctx: event.ctx,
|
|
82
|
+
extra: event.extra,
|
|
83
|
+
headers: event.headers ?? event.extra?.requestInfo?.headers,
|
|
84
|
+
authInfo: event.authInfo ?? event.extra?.authInfo,
|
|
85
|
+
});
|
|
86
|
+
if (!context)
|
|
87
|
+
return;
|
|
88
|
+
const finishedAtMs = Date.now();
|
|
89
|
+
const startedAt = (0, events_js_1.normalizeStartedAt)({
|
|
90
|
+
startedAt: event.startedAt,
|
|
91
|
+
finishedAtMs,
|
|
92
|
+
});
|
|
93
|
+
const batch = (0, events_js_1.buildSessionInitBatch)({
|
|
94
|
+
mcpServerId: context.mcpServerId,
|
|
95
|
+
actorId: context.actorId,
|
|
96
|
+
sessionId,
|
|
97
|
+
requestId: event.requestId ?? (0, node_crypto_1.randomUUID)(),
|
|
98
|
+
startedAt,
|
|
99
|
+
extra: event.extra,
|
|
100
|
+
sessionInitKeys,
|
|
101
|
+
clientInfo: event.clientInfo,
|
|
102
|
+
});
|
|
103
|
+
if (batch)
|
|
104
|
+
await emitBatch(batch);
|
|
105
|
+
};
|
|
106
|
+
const recordToolCall = async (event) => {
|
|
107
|
+
const context = await analyticsContextFor({
|
|
108
|
+
ctx: event.ctx,
|
|
109
|
+
extra: event.extra,
|
|
110
|
+
headers: event.headers ?? event.extra?.requestInfo?.headers,
|
|
111
|
+
authInfo: event.authInfo ?? event.extra?.authInfo,
|
|
112
|
+
toolName: event.name,
|
|
113
|
+
telemetry: event.telemetry,
|
|
114
|
+
});
|
|
115
|
+
if (!context)
|
|
116
|
+
return;
|
|
117
|
+
const finishedAtMs = Date.now();
|
|
118
|
+
const finishedAt = new Date(finishedAtMs).toISOString();
|
|
119
|
+
const durationMs = event.durationMs ?? 0;
|
|
120
|
+
const startedAt = (0, events_js_1.normalizeStartedAt)({
|
|
121
|
+
startedAt: event.startedAt,
|
|
122
|
+
durationMs,
|
|
123
|
+
finishedAtMs,
|
|
124
|
+
});
|
|
125
|
+
const requestId = (0, events_js_1.normalizeRequestId)(event.requestId, event.extra);
|
|
126
|
+
const sessionId = (0, events_js_1.normalizeSessionId)(event.sessionId, event.extra);
|
|
127
|
+
const errorMessage = event.error === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: event.error instanceof Error
|
|
130
|
+
? event.error.message
|
|
131
|
+
: String(event.error);
|
|
132
|
+
const toolCallEvent = (0, events_js_1.buildToolCallEvent)({
|
|
133
|
+
toolName: event.name,
|
|
134
|
+
telemetry: event.telemetry,
|
|
135
|
+
input: event.args,
|
|
136
|
+
output: event.result,
|
|
137
|
+
status: event.status,
|
|
138
|
+
durationMs,
|
|
139
|
+
errorMessage,
|
|
140
|
+
mcpServerId: context.mcpServerId,
|
|
141
|
+
actorId: context.actorId,
|
|
142
|
+
sessionId,
|
|
143
|
+
requestId,
|
|
144
|
+
startedAt,
|
|
145
|
+
finishedAt,
|
|
146
|
+
});
|
|
147
|
+
await emitBatch((0, events_js_1.buildBatch)({
|
|
148
|
+
event: toolCallEvent,
|
|
149
|
+
extra: {
|
|
150
|
+
...(event.extra ?? {}),
|
|
151
|
+
...(sessionId ? { sessionId } : {}),
|
|
152
|
+
},
|
|
153
|
+
mcpServerId: context.mcpServerId,
|
|
154
|
+
actorId: context.actorId,
|
|
155
|
+
startedAt,
|
|
156
|
+
sessionInitKeys,
|
|
157
|
+
clientInfo: event.clientInfo,
|
|
158
|
+
}));
|
|
159
|
+
};
|
|
160
|
+
const registeredTools = new Map();
|
|
161
|
+
const instrumentToolCall = async (event, handler) => {
|
|
162
|
+
const { args, telemetry } = (0, schema_js_1.extractTelemetryArguments)(event.args);
|
|
163
|
+
const startedAtMs = Date.now();
|
|
164
|
+
const startedAt = new Date(startedAtMs).toISOString();
|
|
165
|
+
try {
|
|
166
|
+
const result = await handler(args);
|
|
167
|
+
await recordToolCall({
|
|
168
|
+
...event,
|
|
169
|
+
args,
|
|
170
|
+
telemetry,
|
|
171
|
+
startedAt,
|
|
172
|
+
durationMs: Date.now() - startedAtMs,
|
|
173
|
+
status: "ok",
|
|
174
|
+
result,
|
|
175
|
+
});
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
await recordToolCall({
|
|
180
|
+
...event,
|
|
181
|
+
args,
|
|
182
|
+
telemetry,
|
|
183
|
+
startedAt,
|
|
184
|
+
durationMs: Date.now() - startedAtMs,
|
|
185
|
+
status: "error",
|
|
186
|
+
error,
|
|
187
|
+
});
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const dispatch = async (name, rawArgs, context = {}) => {
|
|
192
|
+
const tool = registeredTools.get(name);
|
|
193
|
+
if (!tool) {
|
|
194
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
195
|
+
}
|
|
196
|
+
return instrumentToolCall({ name, args: rawArgs, ...context }, (args) => tool.handler(args, context));
|
|
197
|
+
};
|
|
198
|
+
let attachedServer = null;
|
|
199
|
+
const buildHandlerContext = (extra) => ({
|
|
200
|
+
extra,
|
|
201
|
+
sessionId: extra?.sessionId,
|
|
202
|
+
requestId: extra?.requestId === undefined ? undefined : String(extra.requestId),
|
|
203
|
+
authInfo: extra?.authInfo,
|
|
204
|
+
headers: extra?.requestInfo?.headers,
|
|
205
|
+
ctx: extra,
|
|
206
|
+
});
|
|
207
|
+
const registerWithServer = (server, registration, handler) => {
|
|
208
|
+
const originalHasInputSchema = registration.inputSchema !== undefined;
|
|
209
|
+
const decoratedSchema = (0, schema_js_1.decorateInputSchemaWithTelemetry)(registration.inputSchema, config);
|
|
210
|
+
server.registerTool(registration.name, {
|
|
211
|
+
...(registration.title !== undefined ? { title: registration.title } : {}),
|
|
212
|
+
...(registration.description !== undefined
|
|
213
|
+
? { description: registration.description }
|
|
214
|
+
: {}),
|
|
215
|
+
inputSchema: decoratedSchema,
|
|
216
|
+
}, (async (...callbackArgs) => {
|
|
217
|
+
const argsOrExtra = callbackArgs[0];
|
|
218
|
+
const maybeExtra = callbackArgs[1];
|
|
219
|
+
const rawArgs = originalHasInputSchema ? argsOrExtra : {};
|
|
220
|
+
const extra = (originalHasInputSchema ? maybeExtra : argsOrExtra);
|
|
221
|
+
return instrumentToolCall({
|
|
222
|
+
name: registration.name,
|
|
223
|
+
args: rawArgs,
|
|
224
|
+
extra,
|
|
225
|
+
sessionId: extra?.sessionId,
|
|
226
|
+
}, (strippedArgs) => handler(strippedArgs, buildHandlerContext(extra)));
|
|
227
|
+
}));
|
|
228
|
+
};
|
|
229
|
+
const tool = (registration, handler) => {
|
|
230
|
+
registeredTools.set(registration.name, {
|
|
231
|
+
registration,
|
|
232
|
+
handler: handler,
|
|
233
|
+
});
|
|
234
|
+
if (attachedServer) {
|
|
235
|
+
registerWithServer(attachedServer, registration, handler);
|
|
236
|
+
}
|
|
237
|
+
return (rawArgs, context = {}) => dispatch(registration.name, rawArgs, context);
|
|
238
|
+
};
|
|
239
|
+
const attachToMcpServer = (server) => {
|
|
240
|
+
if (attachedServer) {
|
|
241
|
+
throw new Error("This recorder is already attached to an McpServer.");
|
|
242
|
+
}
|
|
243
|
+
attachedServer = server;
|
|
244
|
+
for (const { registration, handler } of registeredTools.values()) {
|
|
245
|
+
registerWithServer(server, registration, handler);
|
|
246
|
+
}
|
|
247
|
+
return server;
|
|
248
|
+
};
|
|
249
|
+
const createMcpServer = (info) => {
|
|
250
|
+
return attachToMcpServer(new mcp_js_1.McpServer(info));
|
|
251
|
+
};
|
|
252
|
+
const toolDefinitions = () => {
|
|
253
|
+
return decorateDefinitions(Array.from(registeredTools.values()).map(({ registration }) => {
|
|
254
|
+
const definition = { name: registration.name };
|
|
255
|
+
if (registration.title !== undefined)
|
|
256
|
+
definition.title = registration.title;
|
|
257
|
+
if (registration.description !== undefined) {
|
|
258
|
+
definition.description = registration.description;
|
|
259
|
+
}
|
|
260
|
+
if (registration.inputSchema !== undefined) {
|
|
261
|
+
definition.inputSchema = registration.inputSchema;
|
|
262
|
+
}
|
|
263
|
+
return definition;
|
|
264
|
+
}));
|
|
265
|
+
};
|
|
266
|
+
const hasTool = (name) => registeredTools.has(name);
|
|
267
|
+
return {
|
|
268
|
+
decorateDefinitions,
|
|
269
|
+
extractTelemetry: schema_js_1.extractTelemetryArguments,
|
|
270
|
+
recordToolCall,
|
|
271
|
+
recordSessionInit,
|
|
272
|
+
instrumentToolCall,
|
|
273
|
+
tool,
|
|
274
|
+
dispatch,
|
|
275
|
+
toolDefinitions,
|
|
276
|
+
hasTool,
|
|
277
|
+
attachToMcpServer,
|
|
278
|
+
createMcpServer,
|
|
279
|
+
flush,
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
exports.createAnalyticsRecorder = createAnalyticsRecorder;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ExtractedToolArguments, JsonObjectSchema, McpAnalyticsConfig } from "./types.js";
|
|
3
|
+
export declare const createTelemetryInputSchema: (config?: McpAnalyticsConfig) => z.ZodObject<{
|
|
4
|
+
intent: z.ZodString;
|
|
5
|
+
context: z.ZodOptional<z.ZodString>;
|
|
6
|
+
frustration_level: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
intent: string;
|
|
9
|
+
context?: string | undefined;
|
|
10
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
11
|
+
}, {
|
|
12
|
+
intent: string;
|
|
13
|
+
context?: string | undefined;
|
|
14
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
15
|
+
}> | z.ZodObject<{
|
|
16
|
+
intent: z.ZodOptional<z.ZodString>;
|
|
17
|
+
context: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
18
|
+
frustration_level: z.ZodOptional<z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
intent?: string | undefined;
|
|
21
|
+
context?: string | undefined;
|
|
22
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
intent?: string | undefined;
|
|
25
|
+
context?: string | undefined;
|
|
26
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export declare const createTelemetryJsonSchema: (config?: McpAnalyticsConfig) => JsonObjectSchema;
|
|
29
|
+
export declare const decorateInputSchemaWithTelemetry: (inputSchema: unknown, config?: McpAnalyticsConfig) => JsonObjectSchema | z.ZodObject<{
|
|
30
|
+
[x: string]: any;
|
|
31
|
+
} & {
|
|
32
|
+
telemetry: z.ZodObject<{
|
|
33
|
+
intent: z.ZodString;
|
|
34
|
+
context: z.ZodOptional<z.ZodString>;
|
|
35
|
+
frustration_level: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
intent: string;
|
|
38
|
+
context?: string | undefined;
|
|
39
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
40
|
+
}, {
|
|
41
|
+
intent: string;
|
|
42
|
+
context?: string | undefined;
|
|
43
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
44
|
+
}> | z.ZodObject<{
|
|
45
|
+
intent: z.ZodOptional<z.ZodString>;
|
|
46
|
+
context: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
47
|
+
frustration_level: z.ZodOptional<z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>>;
|
|
48
|
+
}, "strip", z.ZodTypeAny, {
|
|
49
|
+
intent?: string | undefined;
|
|
50
|
+
context?: string | undefined;
|
|
51
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
52
|
+
}, {
|
|
53
|
+
intent?: string | undefined;
|
|
54
|
+
context?: string | undefined;
|
|
55
|
+
frustration_level?: "low" | "medium" | "high" | undefined;
|
|
56
|
+
}>;
|
|
57
|
+
}, any, any, {
|
|
58
|
+
[x: string]: any;
|
|
59
|
+
telemetry?: unknown;
|
|
60
|
+
}, {
|
|
61
|
+
[x: string]: any;
|
|
62
|
+
telemetry?: unknown;
|
|
63
|
+
}>;
|
|
64
|
+
export declare const extractTelemetryArguments: (args: unknown) => ExtractedToolArguments;
|