@posthog/mcp 0.10.0 → 0.10.2
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/dist/extensions/capture.d.ts.map +1 -1
- package/dist/extensions/capture.js +2 -2
- package/dist/extensions/capture.mjs +2 -2
- package/dist/extensions/client-identity.d.ts +39 -0
- package/dist/extensions/client-identity.d.ts.map +1 -0
- package/dist/extensions/client-identity.js +68 -0
- package/dist/extensions/client-identity.mjs +25 -0
- package/dist/extensions/instrumentation.d.ts.map +1 -1
- package/dist/extensions/instrumentation.js +4 -0
- package/dist/extensions/instrumentation.mjs +4 -0
- package/dist/extensions/internal.d.ts.map +1 -1
- package/dist/extensions/internal.js +2 -0
- package/dist/extensions/internal.mjs +2 -0
- package/dist/extensions/mcp-payloads.d.ts.map +1 -1
- package/dist/extensions/mcp-payloads.js +17 -1
- package/dist/extensions/mcp-payloads.mjs +17 -1
- package/dist/extensions/sanitization.d.ts.map +1 -1
- package/dist/extensions/sanitization.js +11 -0
- package/dist/extensions/sanitization.mjs +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.mjs +1 -1
- package/package.json +3 -3
- package/src/extensions/capture.ts +5 -2
- package/src/extensions/client-identity.ts +81 -0
- package/src/extensions/instrumentation.ts +10 -0
- package/src/extensions/internal.ts +2 -0
- package/src/extensions/mcp-payloads.ts +28 -2
- package/src/extensions/sanitization.ts +23 -1
- package/src/version.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/extensions/capture.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAMvD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/extensions/capture.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAMvD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAkEnG"}
|
|
@@ -49,8 +49,8 @@ function captureEvent(server, eventInput) {
|
|
|
49
49
|
sdkVersion: sessionInfo.sdkVersion,
|
|
50
50
|
serverName: sessionInfo.serverName,
|
|
51
51
|
serverVersion: sessionInfo.serverVersion,
|
|
52
|
-
clientName: sessionInfo.clientName,
|
|
53
|
-
clientVersion: sessionInfo.clientVersion,
|
|
52
|
+
clientName: eventInput.clientName ?? sessionInfo.clientName,
|
|
53
|
+
clientVersion: eventInput.clientVersion ?? sessionInfo.clientVersion,
|
|
54
54
|
identifyActorGivenId: sessionInfo.identifyActorGivenId,
|
|
55
55
|
identifyActorData: sessionInfo.identifyActorData,
|
|
56
56
|
groups: sessionInfo.identifyActorGroups,
|
|
@@ -21,8 +21,8 @@ function captureEvent(server, eventInput) {
|
|
|
21
21
|
sdkVersion: sessionInfo.sdkVersion,
|
|
22
22
|
serverName: sessionInfo.serverName,
|
|
23
23
|
serverVersion: sessionInfo.serverVersion,
|
|
24
|
-
clientName: sessionInfo.clientName,
|
|
25
|
-
clientVersion: sessionInfo.clientVersion,
|
|
24
|
+
clientName: eventInput.clientName ?? sessionInfo.clientName,
|
|
25
|
+
clientVersion: eventInput.clientVersion ?? sessionInfo.clientVersion,
|
|
26
26
|
identifyActorGivenId: sessionInfo.identifyActorGivenId,
|
|
27
27
|
identifyActorData: sessionInfo.identifyActorData,
|
|
28
28
|
groups: sessionInfo.identifyActorGroups,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { McpEvent, MCPRequestLike } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Client identity under the MCP 2026-07-28 (stateless) revision.
|
|
4
|
+
*
|
|
5
|
+
* That revision removes the `initialize` handshake and the `Mcp-Session-Id`
|
|
6
|
+
* header (SEP-2575 / SEP-2567). Client name/version and the protocol version no
|
|
7
|
+
* longer arrive once at `initialize` — they travel in every request's
|
|
8
|
+
* `params._meta` under these reverse-DNS keys. We mirror the literal key strings
|
|
9
|
+
* here rather than depend on the (still beta) v2 SDK, which is not a dependency
|
|
10
|
+
* of this package.
|
|
11
|
+
*/
|
|
12
|
+
export declare const META_CLIENT_INFO_KEY = "io.modelcontextprotocol/clientInfo";
|
|
13
|
+
export declare const META_PROTOCOL_VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
14
|
+
export interface MetaClientInfo {
|
|
15
|
+
clientName?: string;
|
|
16
|
+
clientVersion?: string;
|
|
17
|
+
protocolVersion?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Reads the client name/version and protocol version a modern client puts in
|
|
21
|
+
* `params._meta`. Returns `undefined` when the request carries none (e.g. a
|
|
22
|
+
* legacy client, which sends this on `initialize` instead). Never throws.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readMetaClientInfo(request: MCPRequestLike): MetaClientInfo | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Stamps any client identity found in `params._meta` directly onto the event
|
|
27
|
+
* being built for *this* request, so it carries `$mcp_client_name`,
|
|
28
|
+
* `$mcp_client_version`, and `$mcp_protocol_version` even when there was no
|
|
29
|
+
* `initialize` to learn them from (the modern stateless case).
|
|
30
|
+
*
|
|
31
|
+
* Writing to the event — a per-request object — rather than the server-wide
|
|
32
|
+
* `sessionInfo` keeps identity correct when one instrumented server multiplexes
|
|
33
|
+
* concurrent requests from different clients (which the stateless spec allows):
|
|
34
|
+
* a sibling request can't clobber this event's attribution between now and when
|
|
35
|
+
* it's captured. Only fields the request actually carries are set, so a request
|
|
36
|
+
* without `_meta` leaves the event's existing values untouched.
|
|
37
|
+
*/
|
|
38
|
+
export declare function stampMetaClientInfo(event: McpEvent, request: MCPRequestLike): void;
|
|
39
|
+
//# sourceMappingURL=client-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-identity.d.ts","sourceRoot":"","sources":["../../src/extensions/client-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAExD;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,uCAAuC,CAAA;AACxE,eAAO,MAAM,yBAAyB,4CAA4C,CAAA;AAElF,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,GAAG,SAAS,CAyBtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAclF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
META_CLIENT_INFO_KEY: ()=>META_CLIENT_INFO_KEY,
|
|
28
|
+
META_PROTOCOL_VERSION_KEY: ()=>META_PROTOCOL_VERSION_KEY,
|
|
29
|
+
readMetaClientInfo: ()=>readMetaClientInfo,
|
|
30
|
+
stampMetaClientInfo: ()=>stampMetaClientInfo
|
|
31
|
+
});
|
|
32
|
+
const META_CLIENT_INFO_KEY = 'io.modelcontextprotocol/clientInfo';
|
|
33
|
+
const META_PROTOCOL_VERSION_KEY = 'io.modelcontextprotocol/protocolVersion';
|
|
34
|
+
function readMetaClientInfo(request) {
|
|
35
|
+
const meta = request.params?._meta;
|
|
36
|
+
if (!meta || 'object' != typeof meta) return;
|
|
37
|
+
const record = meta;
|
|
38
|
+
const result = {};
|
|
39
|
+
const clientInfo = record[META_CLIENT_INFO_KEY];
|
|
40
|
+
if (clientInfo && 'object' == typeof clientInfo) {
|
|
41
|
+
const { name, version } = clientInfo;
|
|
42
|
+
if ('string' == typeof name && name.length > 0) result.clientName = name;
|
|
43
|
+
if ('string' == typeof version && version.length > 0) result.clientVersion = version;
|
|
44
|
+
}
|
|
45
|
+
const protocolVersion = record[META_PROTOCOL_VERSION_KEY];
|
|
46
|
+
if ('string' == typeof protocolVersion && protocolVersion.length > 0) result.protocolVersion = protocolVersion;
|
|
47
|
+
return result.clientName || result.clientVersion || result.protocolVersion ? result : void 0;
|
|
48
|
+
}
|
|
49
|
+
function stampMetaClientInfo(event, request) {
|
|
50
|
+
const info = readMetaClientInfo(request);
|
|
51
|
+
if (!info) return;
|
|
52
|
+
if (info.clientName) event.clientName = info.clientName;
|
|
53
|
+
if (info.clientVersion) event.clientVersion = info.clientVersion;
|
|
54
|
+
if (info.protocolVersion) event.protocolVersion = info.protocolVersion;
|
|
55
|
+
}
|
|
56
|
+
exports.META_CLIENT_INFO_KEY = __webpack_exports__.META_CLIENT_INFO_KEY;
|
|
57
|
+
exports.META_PROTOCOL_VERSION_KEY = __webpack_exports__.META_PROTOCOL_VERSION_KEY;
|
|
58
|
+
exports.readMetaClientInfo = __webpack_exports__.readMetaClientInfo;
|
|
59
|
+
exports.stampMetaClientInfo = __webpack_exports__.stampMetaClientInfo;
|
|
60
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
61
|
+
"META_CLIENT_INFO_KEY",
|
|
62
|
+
"META_PROTOCOL_VERSION_KEY",
|
|
63
|
+
"readMetaClientInfo",
|
|
64
|
+
"stampMetaClientInfo"
|
|
65
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
66
|
+
Object.defineProperty(exports, '__esModule', {
|
|
67
|
+
value: true
|
|
68
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const META_CLIENT_INFO_KEY = 'io.modelcontextprotocol/clientInfo';
|
|
2
|
+
const META_PROTOCOL_VERSION_KEY = 'io.modelcontextprotocol/protocolVersion';
|
|
3
|
+
function readMetaClientInfo(request) {
|
|
4
|
+
const meta = request.params?._meta;
|
|
5
|
+
if (!meta || 'object' != typeof meta) return;
|
|
6
|
+
const record = meta;
|
|
7
|
+
const result = {};
|
|
8
|
+
const clientInfo = record[META_CLIENT_INFO_KEY];
|
|
9
|
+
if (clientInfo && 'object' == typeof clientInfo) {
|
|
10
|
+
const { name, version } = clientInfo;
|
|
11
|
+
if ('string' == typeof name && name.length > 0) result.clientName = name;
|
|
12
|
+
if ('string' == typeof version && version.length > 0) result.clientVersion = version;
|
|
13
|
+
}
|
|
14
|
+
const protocolVersion = record[META_PROTOCOL_VERSION_KEY];
|
|
15
|
+
if ('string' == typeof protocolVersion && protocolVersion.length > 0) result.protocolVersion = protocolVersion;
|
|
16
|
+
return result.clientName || result.clientVersion || result.protocolVersion ? result : void 0;
|
|
17
|
+
}
|
|
18
|
+
function stampMetaClientInfo(event, request) {
|
|
19
|
+
const info = readMetaClientInfo(request);
|
|
20
|
+
if (!info) return;
|
|
21
|
+
if (info.clientName) event.clientName = info.clientName;
|
|
22
|
+
if (info.clientVersion) event.clientVersion = info.clientVersion;
|
|
23
|
+
if (info.protocolVersion) event.protocolVersion = info.protocolVersion;
|
|
24
|
+
}
|
|
25
|
+
export { META_CLIENT_INFO_KEY, META_PROTOCOL_VERSION_KEY, readMetaClientInfo, stampMetaClientInfo };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAGd,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/extensions/instrumentation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAGd,MAAM,UAAU,CAAA;AAYjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAYrD;;;;;;GAMG;AAEH,KAAK,iBAAiB,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,6BAA6B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE7G;;;;GAIG;AACH,KAAK,YAAY,GAAG,CAAC,iBAAiB,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAE3E,UAAU,mBAAmB;IAC3B,MAAM,EAAE,aAAa,CAAA;IACrB,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,CAAC,EAAE,6BAA6B,CAAA;IACrC,OAAO,EAAE,YAAY,CAAA;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAA;CAClC;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAuCnF;AAoHD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,aAAa,EACrB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,6BAA6B,GAAG,SAAS,KAC7C,OAAO,CAAC,OAAO,CAAC,CAAA;AAErB;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CAqBvG;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,aAAa,EACrB,wBAAwB,EAAE,iBAAiB,EAC3C,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC;IAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAyC9C;AAwDD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CASnH;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,CAUjH;AA8GD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,aAAa,EACrB,yBAAyB,EAAE,iBAAiB,EAC5C,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,OAAO,CAAC,CA0ClB"}
|
|
@@ -34,6 +34,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
34
34
|
});
|
|
35
35
|
const external_context_parameters_js_namespaceObject = require("./context-parameters.js");
|
|
36
36
|
const external_conversation_id_js_namespaceObject = require("./conversation-id.js");
|
|
37
|
+
const external_client_identity_js_namespaceObject = require("./client-identity.js");
|
|
37
38
|
const external_capture_js_namespaceObject = require("./capture.js");
|
|
38
39
|
const external_event_types_js_namespaceObject = require("./event-types.js");
|
|
39
40
|
const external_exceptions_js_namespaceObject = require("./exceptions.js");
|
|
@@ -79,6 +80,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
|
|
|
79
80
|
toolCategory: toolName ? data.toolCategories.get(toolName) : void 0,
|
|
80
81
|
toolDescription: toolName ? data.toolDescriptions.get(toolName) : void 0
|
|
81
82
|
};
|
|
83
|
+
(0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(event, request);
|
|
82
84
|
await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
|
|
83
85
|
(0, external_intent_js_namespaceObject.setEventIntent)(event, await (0, external_intent_js_namespaceObject.resolveToolCallIntent)(data, request, extra));
|
|
84
86
|
return event;
|
|
@@ -143,6 +145,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
143
145
|
eventType: external_event_types_js_namespaceObject.MCPAnalyticsEventType.mcpToolsList,
|
|
144
146
|
timestamp: startTime
|
|
145
147
|
};
|
|
148
|
+
(0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(event, request);
|
|
146
149
|
if (data) await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
|
|
147
150
|
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event);
|
|
148
151
|
if (!data) {
|
|
@@ -288,6 +291,7 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
|
|
|
288
291
|
parameters: (0, external_mcp_payloads_js_namespaceObject.buildCapturedMcpParameters)(request),
|
|
289
292
|
timestamp: new Date()
|
|
290
293
|
};
|
|
294
|
+
(0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(event, request);
|
|
291
295
|
await (0, external_tracing_helpers_js_namespaceObject.applyResolvedMetadata)(event, data, request, extra);
|
|
292
296
|
const result = await originalInitializeHandler(request, extra);
|
|
293
297
|
event.response = result;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { addContextParameterToTools, getContextDescription, isContextEnabled } from "./context-parameters.mjs";
|
|
2
2
|
import { addConversationIdToTools, canInjectConversationIdPromptBack, cloneRequestWithoutConversationId, injectConversationIdPromptBack, resolveConversationId } from "./conversation-id.mjs";
|
|
3
|
+
import { stampMetaClientInfo } from "./client-identity.mjs";
|
|
3
4
|
import { captureEvent } from "./capture.mjs";
|
|
4
5
|
import { MCPAnalyticsEventType } from "./event-types.mjs";
|
|
5
6
|
import { captureException } from "./exceptions.mjs";
|
|
@@ -45,6 +46,7 @@ async function prepareToolCallEvent(server, data, request, downstreamRequest, ex
|
|
|
45
46
|
toolCategory: toolName ? data.toolCategories.get(toolName) : void 0,
|
|
46
47
|
toolDescription: toolName ? data.toolDescriptions.get(toolName) : void 0
|
|
47
48
|
};
|
|
49
|
+
stampMetaClientInfo(event, request);
|
|
48
50
|
await applyResolvedMetadata(event, data, request, extra);
|
|
49
51
|
setEventIntent(event, await resolveToolCallIntent(data, request, extra));
|
|
50
52
|
return event;
|
|
@@ -109,6 +111,7 @@ async function handleListToolsRequest(server, originalListToolsHandler, request,
|
|
|
109
111
|
eventType: MCPAnalyticsEventType.mcpToolsList,
|
|
110
112
|
timestamp: startTime
|
|
111
113
|
};
|
|
114
|
+
stampMetaClientInfo(event, request);
|
|
112
115
|
if (data) await applyResolvedMetadata(event, data, request, extra);
|
|
113
116
|
const tools = await getTracedToolsList(server, originalListToolsHandler, request, extra, event);
|
|
114
117
|
if (!data) {
|
|
@@ -254,6 +257,7 @@ async function handleInitializeRequest(server, originalInitializeHandler, reques
|
|
|
254
257
|
parameters: buildCapturedMcpParameters(request),
|
|
255
258
|
timestamp: new Date()
|
|
256
259
|
};
|
|
260
|
+
stampMetaClientInfo(event, request);
|
|
257
261
|
await applyResolvedMetadata(event, data, request, extra);
|
|
258
262
|
const result = await originalInitializeHandler(request, extra);
|
|
259
263
|
event.response = result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/extensions/internal.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAEb,YAAY,EACb,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/extensions/internal.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,cAAc,EACd,aAAa,EAEb,YAAY,EACb,MAAM,UAAU,CAAA;AAMjB;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,OAAO,SAAO;IAI1B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAWhD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAapD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B,IAAI,IAAI,MAAM;CAGf;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,GAAG,SAAS,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAEzF;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,OAAO,CA4B5E;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,YAAY,CAapG;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CAgDf;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,cAAc,EACvB,KAAK,CAAC,EAAE,6BAA6B,GACpC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAUzC"}
|
|
@@ -35,6 +35,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
35
35
|
const external_event_types_js_namespaceObject = require("./event-types.js");
|
|
36
36
|
const external_logger_js_namespaceObject = require("./logger.js");
|
|
37
37
|
const external_capture_js_namespaceObject = require("./capture.js");
|
|
38
|
+
const external_client_identity_js_namespaceObject = require("./client-identity.js");
|
|
38
39
|
class IdentityCache {
|
|
39
40
|
constructor(maxSize = 1000){
|
|
40
41
|
this._cache = new Map();
|
|
@@ -106,6 +107,7 @@ async function handleIdentify(server, data, sessionId, request, extra) {
|
|
|
106
107
|
},
|
|
107
108
|
timestamp: new Date()
|
|
108
109
|
};
|
|
110
|
+
(0, external_client_identity_js_namespaceObject.stampMetaClientInfo)(identifyEvent, request);
|
|
109
111
|
try {
|
|
110
112
|
const identityResult = 'function' == typeof data.options.identify ? await data.options.identify(request, extra) : data.options.identify;
|
|
111
113
|
if (identityResult) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MCPAnalyticsEventType } from "./event-types.mjs";
|
|
2
2
|
import { log } from "./logger.mjs";
|
|
3
3
|
import { captureEvent } from "./capture.mjs";
|
|
4
|
+
import { stampMetaClientInfo } from "./client-identity.mjs";
|
|
4
5
|
class IdentityCache {
|
|
5
6
|
constructor(maxSize = 1000){
|
|
6
7
|
this._cache = new Map();
|
|
@@ -72,6 +73,7 @@ async function handleIdentify(server, data, sessionId, request, extra) {
|
|
|
72
73
|
},
|
|
73
74
|
timestamp: new Date()
|
|
74
75
|
};
|
|
76
|
+
stampMetaClientInfo(identifyEvent, request);
|
|
75
77
|
try {
|
|
76
78
|
const identityResult = 'function' == typeof data.options.identify ? await data.options.identify(request, extra) : data.options.identify;
|
|
77
79
|
if (identityResult) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-payloads.d.ts","sourceRoot":"","sources":["../../src/extensions/mcp-payloads.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp-payloads.d.ts","sourceRoot":"","sources":["../../src/extensions/mcp-payloads.ts"],"names":[],"mappings":"AAkBA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAsCzC,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA0B7D;AA6BD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAiBvE"}
|
|
@@ -29,7 +29,12 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
29
29
|
});
|
|
30
30
|
const CONTEXT_ARGUMENT_NAME = 'context';
|
|
31
31
|
const REDACTED_VALUE = '[redacted]';
|
|
32
|
+
const BINARY_REDACTED_VALUE = '[binary data redacted - not supported by PostHog MCP analytics]';
|
|
32
33
|
const BASE64_PATTERN = /^[A-Za-z0-9+/\n\r]+=*$/;
|
|
34
|
+
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+={0,2}$/;
|
|
35
|
+
const BASE64URL_SPECIFIC_CHAR_PATTERN = /[-_]/;
|
|
36
|
+
const BASE64_DATA_URL_PREFIX_PATTERN = /^data:[^,\s]*;base64,/i;
|
|
37
|
+
const BASE64_DATA_URL_PAYLOAD_PATTERN = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
33
38
|
const SIZE_GATE = 10240;
|
|
34
39
|
const POSTHOG_TOKEN_PATTERN = /\bph[a-z]_[A-Za-z0-9_-]{20,}\b/g;
|
|
35
40
|
const SENSITIVE_KEY_PATTERN = /^(authorization|cookie|set-cookie|x-api-key|api[-_]?key|api[-_]?token|access[-_]?token|refresh[-_]?token|token|password|secret|client[-_]?secret|private[-_]?key)$/i;
|
|
@@ -39,8 +44,19 @@ function isRecord(value) {
|
|
|
39
44
|
function shouldRedactKey(key) {
|
|
40
45
|
return SENSITIVE_KEY_PATTERN.test(key);
|
|
41
46
|
}
|
|
47
|
+
function isBase64DataUrl(value) {
|
|
48
|
+
const prefix = BASE64_DATA_URL_PREFIX_PATTERN.exec(value);
|
|
49
|
+
if (!prefix) return false;
|
|
50
|
+
let payload;
|
|
51
|
+
try {
|
|
52
|
+
payload = decodeURIComponent(value.slice(prefix[0].length));
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return BASE64_DATA_URL_PAYLOAD_PATTERN.test(payload.replace(/[\r\n]/g, ''));
|
|
57
|
+
}
|
|
42
58
|
function sanitizeString(value) {
|
|
43
|
-
if (value.length >= SIZE_GATE && BASE64_PATTERN.test(value)
|
|
59
|
+
if (value.length >= SIZE_GATE && (BASE64_PATTERN.test(value) || isBase64DataUrl(value) || BASE64URL_SPECIFIC_CHAR_PATTERN.test(value) && BASE64URL_PATTERN.test(value))) return BINARY_REDACTED_VALUE;
|
|
44
60
|
return value.replace(POSTHOG_TOKEN_PATTERN, REDACTED_VALUE);
|
|
45
61
|
}
|
|
46
62
|
function sanitizeCapturedValue(value) {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const CONTEXT_ARGUMENT_NAME = 'context';
|
|
2
2
|
const REDACTED_VALUE = '[redacted]';
|
|
3
|
+
const BINARY_REDACTED_VALUE = '[binary data redacted - not supported by PostHog MCP analytics]';
|
|
3
4
|
const BASE64_PATTERN = /^[A-Za-z0-9+/\n\r]+=*$/;
|
|
5
|
+
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+={0,2}$/;
|
|
6
|
+
const BASE64URL_SPECIFIC_CHAR_PATTERN = /[-_]/;
|
|
7
|
+
const BASE64_DATA_URL_PREFIX_PATTERN = /^data:[^,\s]*;base64,/i;
|
|
8
|
+
const BASE64_DATA_URL_PAYLOAD_PATTERN = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
4
9
|
const SIZE_GATE = 10240;
|
|
5
10
|
const POSTHOG_TOKEN_PATTERN = /\bph[a-z]_[A-Za-z0-9_-]{20,}\b/g;
|
|
6
11
|
const SENSITIVE_KEY_PATTERN = /^(authorization|cookie|set-cookie|x-api-key|api[-_]?key|api[-_]?token|access[-_]?token|refresh[-_]?token|token|password|secret|client[-_]?secret|private[-_]?key)$/i;
|
|
@@ -10,8 +15,19 @@ function isRecord(value) {
|
|
|
10
15
|
function shouldRedactKey(key) {
|
|
11
16
|
return SENSITIVE_KEY_PATTERN.test(key);
|
|
12
17
|
}
|
|
18
|
+
function isBase64DataUrl(value) {
|
|
19
|
+
const prefix = BASE64_DATA_URL_PREFIX_PATTERN.exec(value);
|
|
20
|
+
if (!prefix) return false;
|
|
21
|
+
let payload;
|
|
22
|
+
try {
|
|
23
|
+
payload = decodeURIComponent(value.slice(prefix[0].length));
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return BASE64_DATA_URL_PAYLOAD_PATTERN.test(payload.replace(/[\r\n]/g, ''));
|
|
28
|
+
}
|
|
13
29
|
function sanitizeString(value) {
|
|
14
|
-
if (value.length >= SIZE_GATE && BASE64_PATTERN.test(value)
|
|
30
|
+
if (value.length >= SIZE_GATE && (BASE64_PATTERN.test(value) || isBase64DataUrl(value) || BASE64URL_SPECIFIC_CHAR_PATTERN.test(value) && BASE64URL_PATTERN.test(value))) return BINARY_REDACTED_VALUE;
|
|
15
31
|
return value.replace(POSTHOG_TOKEN_PATTERN, REDACTED_VALUE);
|
|
16
32
|
}
|
|
17
33
|
function sanitizeCapturedValue(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitization.d.ts","sourceRoot":"","sources":["../../src/extensions/sanitization.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"sanitization.d.ts","sourceRoot":"","sources":["../../src/extensions/sanitization.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAmB,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAShE;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,GAAG,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAuBrE"}
|
|
@@ -37,8 +37,19 @@ function sanitizeEvent(event) {
|
|
|
37
37
|
if (null != result.response) result.response = sanitizeResponse(result.response);
|
|
38
38
|
if (null != result.parameters) result.parameters = sanitizeParameters(result.parameters);
|
|
39
39
|
if (null != result.userIntent) result.userIntent = (0, external_mcp_payloads_js_namespaceObject.sanitizeCapturedValue)(result.userIntent);
|
|
40
|
+
if (null != result.error) result.error = sanitizeExceptionValues(result.error);
|
|
40
41
|
return result;
|
|
41
42
|
}
|
|
43
|
+
function sanitizeExceptionValues(error) {
|
|
44
|
+
if (!Array.isArray(error.$exception_list)) return error;
|
|
45
|
+
return {
|
|
46
|
+
...error,
|
|
47
|
+
$exception_list: error.$exception_list.map((exception)=>({
|
|
48
|
+
...exception,
|
|
49
|
+
value: (0, external_mcp_payloads_js_namespaceObject.sanitizeCapturedValue)(exception.value)
|
|
50
|
+
}))
|
|
51
|
+
};
|
|
52
|
+
}
|
|
42
53
|
function sanitizeResponse(response) {
|
|
43
54
|
if (null == response || 'object' != typeof response) return (0, external_mcp_payloads_js_namespaceObject.sanitizeCapturedValue)(response);
|
|
44
55
|
const sanitized = (0, external_mcp_payloads_js_namespaceObject.sanitizeCapturedValue)(response);
|
|
@@ -9,8 +9,19 @@ function sanitizeEvent(event) {
|
|
|
9
9
|
if (null != result.response) result.response = sanitizeResponse(result.response);
|
|
10
10
|
if (null != result.parameters) result.parameters = sanitizeParameters(result.parameters);
|
|
11
11
|
if (null != result.userIntent) result.userIntent = sanitizeCapturedValue(result.userIntent);
|
|
12
|
+
if (null != result.error) result.error = sanitizeExceptionValues(result.error);
|
|
12
13
|
return result;
|
|
13
14
|
}
|
|
15
|
+
function sanitizeExceptionValues(error) {
|
|
16
|
+
if (!Array.isArray(error.$exception_list)) return error;
|
|
17
|
+
return {
|
|
18
|
+
...error,
|
|
19
|
+
$exception_list: error.$exception_list.map((exception)=>({
|
|
20
|
+
...exception,
|
|
21
|
+
value: sanitizeCapturedValue(exception.value)
|
|
22
|
+
}))
|
|
23
|
+
};
|
|
24
|
+
}
|
|
14
25
|
function sanitizeResponse(response) {
|
|
15
26
|
if (null == response || 'object' != typeof response) return sanitizeCapturedValue(response);
|
|
16
27
|
const sanitized = sanitizeCapturedValue(response);
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "0.10.
|
|
1
|
+
export declare const version = "0.10.2";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -26,7 +26,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
version: ()=>version
|
|
28
28
|
});
|
|
29
|
-
const version = '0.10.
|
|
29
|
+
const version = '0.10.2';
|
|
30
30
|
exports.version = __webpack_exports__.version;
|
|
31
31
|
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
32
32
|
"version"
|
package/dist/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const version = '0.10.
|
|
1
|
+
const version = '0.10.2';
|
|
2
2
|
export { version };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"bugs": {
|
|
5
5
|
"url": "https://github.com/PostHog/posthog-js/issues"
|
|
6
6
|
},
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"module": "dist/index.mjs",
|
|
29
29
|
"types": "dist/index.d.ts",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@posthog/core": "^1.
|
|
31
|
+
"@posthog/core": "^1.46.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@babel/preset-env": "^7.27.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@types/node": "^20.0.0",
|
|
40
40
|
"jest": "29.7.0",
|
|
41
41
|
"zod": "^3.25.0",
|
|
42
|
-
"posthog-node": "^5.
|
|
42
|
+
"posthog-node": "^5.47.2",
|
|
43
43
|
"@posthog-tooling/tsconfig-base": "1.1.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
@@ -48,8 +48,11 @@ export function captureEvent(server: MCPServerLike, eventInput: McpEvent): Promi
|
|
|
48
48
|
sdkVersion: sessionInfo.sdkVersion,
|
|
49
49
|
serverName: sessionInfo.serverName,
|
|
50
50
|
serverVersion: sessionInfo.serverVersion,
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// Prefer identity the event captured for *this* request (from `_meta`, see
|
|
52
|
+
// stampMetaClientInfo) over the server-wide sessionInfo, so a concurrent
|
|
53
|
+
// request from another client can't misattribute this one.
|
|
54
|
+
clientName: eventInput.clientName ?? sessionInfo.clientName,
|
|
55
|
+
clientVersion: eventInput.clientVersion ?? sessionInfo.clientVersion,
|
|
53
56
|
identifyActorGivenId: sessionInfo.identifyActorGivenId,
|
|
54
57
|
identifyActorData: sessionInfo.identifyActorData,
|
|
55
58
|
groups: sessionInfo.identifyActorGroups,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { McpEvent, MCPRequestLike } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Client identity under the MCP 2026-07-28 (stateless) revision.
|
|
5
|
+
*
|
|
6
|
+
* That revision removes the `initialize` handshake and the `Mcp-Session-Id`
|
|
7
|
+
* header (SEP-2575 / SEP-2567). Client name/version and the protocol version no
|
|
8
|
+
* longer arrive once at `initialize` — they travel in every request's
|
|
9
|
+
* `params._meta` under these reverse-DNS keys. We mirror the literal key strings
|
|
10
|
+
* here rather than depend on the (still beta) v2 SDK, which is not a dependency
|
|
11
|
+
* of this package.
|
|
12
|
+
*/
|
|
13
|
+
export const META_CLIENT_INFO_KEY = 'io.modelcontextprotocol/clientInfo'
|
|
14
|
+
export const META_PROTOCOL_VERSION_KEY = 'io.modelcontextprotocol/protocolVersion'
|
|
15
|
+
|
|
16
|
+
export interface MetaClientInfo {
|
|
17
|
+
clientName?: string
|
|
18
|
+
clientVersion?: string
|
|
19
|
+
protocolVersion?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reads the client name/version and protocol version a modern client puts in
|
|
24
|
+
* `params._meta`. Returns `undefined` when the request carries none (e.g. a
|
|
25
|
+
* legacy client, which sends this on `initialize` instead). Never throws.
|
|
26
|
+
*/
|
|
27
|
+
export function readMetaClientInfo(request: MCPRequestLike): MetaClientInfo | undefined {
|
|
28
|
+
const meta = request.params?._meta
|
|
29
|
+
if (!meta || typeof meta !== 'object') {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
const record = meta as Record<string, unknown>
|
|
33
|
+
const result: MetaClientInfo = {}
|
|
34
|
+
|
|
35
|
+
const clientInfo = record[META_CLIENT_INFO_KEY]
|
|
36
|
+
if (clientInfo && typeof clientInfo === 'object') {
|
|
37
|
+
const { name, version } = clientInfo as Record<string, unknown>
|
|
38
|
+
if (typeof name === 'string' && name.length > 0) {
|
|
39
|
+
result.clientName = name
|
|
40
|
+
}
|
|
41
|
+
if (typeof version === 'string' && version.length > 0) {
|
|
42
|
+
result.clientVersion = version
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const protocolVersion = record[META_PROTOCOL_VERSION_KEY]
|
|
47
|
+
if (typeof protocolVersion === 'string' && protocolVersion.length > 0) {
|
|
48
|
+
result.protocolVersion = protocolVersion
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result.clientName || result.clientVersion || result.protocolVersion ? result : undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Stamps any client identity found in `params._meta` directly onto the event
|
|
56
|
+
* being built for *this* request, so it carries `$mcp_client_name`,
|
|
57
|
+
* `$mcp_client_version`, and `$mcp_protocol_version` even when there was no
|
|
58
|
+
* `initialize` to learn them from (the modern stateless case).
|
|
59
|
+
*
|
|
60
|
+
* Writing to the event — a per-request object — rather than the server-wide
|
|
61
|
+
* `sessionInfo` keeps identity correct when one instrumented server multiplexes
|
|
62
|
+
* concurrent requests from different clients (which the stateless spec allows):
|
|
63
|
+
* a sibling request can't clobber this event's attribution between now and when
|
|
64
|
+
* it's captured. Only fields the request actually carries are set, so a request
|
|
65
|
+
* without `_meta` leaves the event's existing values untouched.
|
|
66
|
+
*/
|
|
67
|
+
export function stampMetaClientInfo(event: McpEvent, request: MCPRequestLike): void {
|
|
68
|
+
const info = readMetaClientInfo(request)
|
|
69
|
+
if (!info) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if (info.clientName) {
|
|
73
|
+
event.clientName = info.clientName
|
|
74
|
+
}
|
|
75
|
+
if (info.clientVersion) {
|
|
76
|
+
event.clientVersion = info.clientVersion
|
|
77
|
+
}
|
|
78
|
+
if (info.protocolVersion) {
|
|
79
|
+
event.protocolVersion = info.protocolVersion
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
injectConversationIdPromptBack,
|
|
22
22
|
resolveConversationId,
|
|
23
23
|
} from './conversation-id'
|
|
24
|
+
import { stampMetaClientInfo } from './client-identity'
|
|
24
25
|
import { captureEvent } from './capture'
|
|
25
26
|
import { MCPAnalyticsEventType } from './event-types'
|
|
26
27
|
import { captureException } from './exceptions'
|
|
@@ -152,6 +153,10 @@ async function prepareToolCallEvent(
|
|
|
152
153
|
toolCategory: toolName ? data.toolCategories.get(toolName) : undefined,
|
|
153
154
|
toolDescription: toolName ? data.toolDescriptions.get(toolName) : undefined,
|
|
154
155
|
}
|
|
156
|
+
// Modern (stateless) clients carry client name/version + protocol version in
|
|
157
|
+
// `_meta` on every request rather than at `initialize`; stamp them onto this
|
|
158
|
+
// event now so concurrent requests can't cross-attribute it.
|
|
159
|
+
stampMetaClientInfo(event, request)
|
|
155
160
|
|
|
156
161
|
await applyResolvedMetadata(event, data, request, extra)
|
|
157
162
|
setEventIntent(event, await resolveToolCallIntent(data, request, extra))
|
|
@@ -297,6 +302,7 @@ export async function handleListToolsRequest(
|
|
|
297
302
|
eventType: MCPAnalyticsEventType.mcpToolsList,
|
|
298
303
|
timestamp: startTime,
|
|
299
304
|
}
|
|
305
|
+
stampMetaClientInfo(event, request)
|
|
300
306
|
|
|
301
307
|
if (data) {
|
|
302
308
|
await applyResolvedMetadata(event, data, request, extra)
|
|
@@ -555,6 +561,10 @@ export async function handleInitializeRequest(
|
|
|
555
561
|
parameters: buildCapturedMcpParameters(request),
|
|
556
562
|
timestamp: new Date(),
|
|
557
563
|
}
|
|
564
|
+
// Harmless for a legacy `initialize` (client info rides the body there, and the
|
|
565
|
+
// negotiated protocol version below overrides any `_meta` one); picks up client
|
|
566
|
+
// info if a client also sends it in `_meta`.
|
|
567
|
+
stampMetaClientInfo(event, request)
|
|
558
568
|
|
|
559
569
|
await applyResolvedMetadata(event, data, request, extra)
|
|
560
570
|
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
import { MCPAnalyticsEventType } from './event-types'
|
|
15
15
|
import { log } from './logger'
|
|
16
16
|
import { captureEvent } from './capture'
|
|
17
|
+
import { stampMetaClientInfo } from './client-identity'
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Bounded LRU cache for session identities, capped at `maxSize` entries so a
|
|
@@ -141,6 +142,7 @@ export async function handleIdentify(
|
|
|
141
142
|
parameters: { request, extra },
|
|
142
143
|
timestamp: new Date(),
|
|
143
144
|
}
|
|
145
|
+
stampMetaClientInfo(identifyEvent, request)
|
|
144
146
|
|
|
145
147
|
try {
|
|
146
148
|
const identityResult =
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
const CONTEXT_ARGUMENT_NAME = 'context'
|
|
7
7
|
const REDACTED_VALUE = '[redacted]'
|
|
8
|
+
const BINARY_REDACTED_VALUE = '[binary data redacted - not supported by PostHog MCP analytics]'
|
|
8
9
|
const BASE64_PATTERN = /^[A-Za-z0-9+/\n\r]+=*$/
|
|
10
|
+
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+={0,2}$/
|
|
11
|
+
const BASE64URL_SPECIFIC_CHAR_PATTERN = /[-_]/
|
|
12
|
+
const BASE64_DATA_URL_PREFIX_PATTERN = /^data:[^,\s]*;base64,/i
|
|
13
|
+
const BASE64_DATA_URL_PAYLOAD_PATTERN = /^[A-Za-z0-9+/_-]+={0,2}$/
|
|
9
14
|
const SIZE_GATE = 10_240
|
|
10
15
|
const POSTHOG_TOKEN_PATTERN = /\bph[a-z]_[A-Za-z0-9_-]{20,}\b/g
|
|
11
16
|
const SENSITIVE_KEY_PATTERN =
|
|
@@ -21,9 +26,30 @@ function shouldRedactKey(key: string): boolean {
|
|
|
21
26
|
return SENSITIVE_KEY_PATTERN.test(key)
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
function isBase64DataUrl(value: string): boolean {
|
|
30
|
+
const prefix = BASE64_DATA_URL_PREFIX_PATTERN.exec(value)
|
|
31
|
+
if (!prefix) {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let payload: string
|
|
36
|
+
try {
|
|
37
|
+
payload = decodeURIComponent(value.slice(prefix[0].length))
|
|
38
|
+
} catch {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return BASE64_DATA_URL_PAYLOAD_PATTERN.test(payload.replace(/[\r\n]/g, ''))
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
function sanitizeString(value: string): string {
|
|
25
|
-
if (
|
|
26
|
-
|
|
46
|
+
if (
|
|
47
|
+
value.length >= SIZE_GATE &&
|
|
48
|
+
(BASE64_PATTERN.test(value) ||
|
|
49
|
+
isBase64DataUrl(value) ||
|
|
50
|
+
(BASE64URL_SPECIFIC_CHAR_PATTERN.test(value) && BASE64URL_PATTERN.test(value)))
|
|
51
|
+
) {
|
|
52
|
+
return BINARY_REDACTED_VALUE
|
|
27
53
|
}
|
|
28
54
|
return value.replace(POSTHOG_TOKEN_PATTERN, REDACTED_VALUE)
|
|
29
55
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Copyright (c) 2025 AgentCat, Inc. (formerly MCPcat)
|
|
4
4
|
// Licensed under the MIT License: https://github.com/agentcathq/agentcat-typescript-sdk/blob/main/LICENSE
|
|
5
5
|
|
|
6
|
-
import type { Event, McpEvent } from '../types'
|
|
6
|
+
import type { ErrorProperties, Event, McpEvent } from '../types'
|
|
7
7
|
import { sanitizeCapturedValue } from './mcp-payloads'
|
|
8
8
|
|
|
9
9
|
type SanitizedRecord = Record<string, unknown>
|
|
@@ -39,9 +39,31 @@ export function sanitizeEvent<T extends Event | McpEvent>(event: T): T {
|
|
|
39
39
|
result.userIntent = sanitizeCapturedValue(result.userIntent) as string
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
if (result.error != null) {
|
|
43
|
+
result.error = sanitizeExceptionValues(result.error)
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
return result
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Sanitizes exception messages before they fan out to both the primary MCP
|
|
51
|
+
* event's error-message property and the `$exception` sibling.
|
|
52
|
+
*/
|
|
53
|
+
function sanitizeExceptionValues(error: ErrorProperties): ErrorProperties {
|
|
54
|
+
if (!Array.isArray(error.$exception_list)) {
|
|
55
|
+
return error
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...error,
|
|
60
|
+
$exception_list: error.$exception_list.map((exception) => ({
|
|
61
|
+
...exception,
|
|
62
|
+
value: sanitizeCapturedValue(exception.value) as string,
|
|
63
|
+
})),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
/**
|
|
46
68
|
* Sanitizes response content blocks by replacing non-text content types
|
|
47
69
|
* with informative redaction messages.
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.10.
|
|
1
|
+
export const version = '0.10.2'
|