@knownagents/sdk 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,79 +33,84 @@ const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN", {
33
33
 
34
34
  The entire visit event queue is uploaded when it reaches `flushQueueSize` or when the flush interval elapses after the first visit event enters an empty queue.
35
35
 
36
- ## Send Visit Events ([Agent Analytics](https://knownagents.com/products/agent-analytics), [LLM Referral Tracking](https://knownagents.com/products/llm-referral-tracking))
37
-
38
- Get realtime insight into how [AI agents and other bots](https://knownagents.com/agents) browse your web pages, call your [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) endpoints, and purchase products through your [ACP (Agentic Commerce Protocol)](https://www.agenticcommerce.dev/) and [UCP (Universal Commerce Protocol)](https://ucp.dev/) shopping flows. Measure human traffic from AI chat and search platforms like ChatGPT, Claude, and Gemini.
39
-
40
- ### Pageviews and REST Calls
41
-
42
- Call `trackPageviewOrRESTCall` with the incoming request and outgoing response. If you can, do this in middleware. This method skips MCP calls automatically.
36
+ Before your application shuts down, call `flush` to upload any queued visit events:
43
37
 
44
38
  ```ts
45
- knownAgents.trackPageviewOrRESTCall(request, response)
39
+ await knownAgents.flush()
46
40
  ```
47
41
 
48
- ### MCP Calls (Optional)
42
+ ## Track Pageviews and API Calls ([Agent Analytics](https://knownagents.com/products/agent-analytics), [LLM Referral Tracking](https://knownagents.com/products/llm-referral-tracking))
49
43
 
50
- For MCP servers that use `StreamableHTTPServerTransport`, call `trackMCPCall` after connecting the transport and before the transport handles the request.
51
-
52
- ```ts
53
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
54
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
44
+ Get realtime insight into how [AI agents and other bots](https://knownagents.com/agents) browse your web pages, use your [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server, and purchase products through your [ACP](https://www.agenticcommerce.dev/) (Agentic Commerce Protocol) and [UCP](https://ucp.dev/) (Universal Commerce Protocol) shopping flows. Measure human traffic from AI chat and search platforms like ChatGPT, Claude, and Gemini.
55
45
 
56
- // ...
46
+ Call `trackVisit` with the incoming request and outgoing response.
57
47
 
58
- const mcpServer = new McpServer({
59
- name: SERVER_NAME,
60
- version: SERVER_VERSION,
61
- })
62
-
63
- const transport = new StreamableHTTPServerTransport({
64
- sessionIdGenerator: undefined
65
- })
48
+ ```ts
49
+ knownAgents.trackVisit(request, response)
50
+ ```
66
51
 
67
- // ...
52
+ If you can, do this in middleware.
68
53
 
69
- await mcpServer.connect(transport)
54
+ ### MCP
70
55
 
71
- knownAgents.trackMCPCall(request, response, transport)
56
+ For MCP endpoints, call `trackVisit` from each tool handler.
72
57
 
73
- await transport.handleRequest(request, response, parsedBody)
58
+ ```ts
59
+ import { createMcpHandler, McpServer } from "@modelcontextprotocol/server"
60
+
61
+ const mcpHandler = createMcpHandler(({ requestInfo }) => {
62
+ const server = new McpServer({
63
+ name: SERVER_NAME,
64
+ version: SERVER_VERSION,
65
+ })
66
+
67
+ server.registerTool("ping", { description: "Check whether the server is available" }, async (context) => {
68
+ const result = {
69
+ content: [{ type: "text", text: "pong" }]
70
+ }
71
+
72
+ knownAgents.trackVisit(requestInfo, undefined, {
73
+ mcp: {
74
+ context: context,
75
+ result: result,
76
+ toolName: "ping"
77
+ }
78
+ })
79
+
80
+ return result
81
+ })
82
+
83
+ return server
84
+ })
74
85
  ```
75
86
 
76
- ### Agentic Commerce Interactions (Optional)
87
+ ### Agentic Commerce
77
88
 
78
- For REST implementations of ACP and UCP, include the response body in `trackPageviewOrRESTCall`.
89
+ For ACP and UCP endpoints, include the response body in `trackVisit`.
79
90
 
80
91
  ```ts
81
92
  // For ACP calls ...
82
93
 
83
- knownAgents.trackPageviewOrRESTCall(request, response, {
84
- acpResponseBody: acpResponseBody
94
+ knownAgents.trackVisit(request, response, {
95
+ acp: {
96
+ responseBody: acpResponseBody
97
+ }
85
98
  })
86
99
  ```
87
100
 
88
101
  ```ts
89
102
  // For UCP calls ...
90
103
 
91
- knownAgents.trackPageviewOrRESTCall(request, response, {
92
- ucpResponseBody: ucpResponseBody
104
+ knownAgents.trackVisit(request, response, {
105
+ ucp: {
106
+ responseBody: ucpResponseBody
107
+ }
93
108
  })
94
109
  ```
95
110
 
96
- `trackMCPCall` will track MCP implementations of ACP and UCP automatically.
97
-
98
- ### Test Your Integration
99
-
100
- - Open your project's settings page
101
- - Click **Send a Test Visit**
102
- - Click **Realtime**
103
-
104
- If your website is correctly connected, you should see visits from a test agent in the realtime timeline within a few seconds.
105
-
106
- ## Set Up Automatic Robots.txt ([Automatic Robots.txt](https://knownagents.com/products/automatic-robots-txt))
111
+ ## Serve an Automatic Robots.txt ([Automatic Robots.txt](https://knownagents.com/products/automatic-robots-txt))
107
112
 
108
- Serve a robots.txt that continuously updates with rules for new [crawlers, scrapers, AI agents, and other bots](https://knownagents.com/agents) as they're discovered.
113
+ Generate a robots.txt that continuously updates with rules for new [crawlers, scrapers, AI agents, and other bots](https://knownagents.com/agents) as they're discovered.
109
114
 
110
115
  Use the `generateRobotsTXT` method. Select which `AgentType`s you want to block, and a string specifying which URLs are disallowed (e.g. `"/"` to disallow all paths).
111
116
 
@@ -122,7 +127,7 @@ const robotsTxt = await knownAgents.generateRobotsTXT([
122
127
 
123
128
  The return value is a plain text robots.txt string. Generate a `robotsTxt` periodically (e.g. once per day, using a cron job). Then, serve it from your website's `/robots.txt` endpoint.
124
129
 
125
- ## Use the Agent Identification API ([Agent Identification API](https://knownagents.com/products/agent-identification-api))
130
+ ## Identify & Verify Agents ([Agent Identification](https://knownagents.com/products/agent-identification-api))
126
131
 
127
132
  Identify and verify [AI agents and other bots](https://knownagents.com/agents) in your own products, from incoming network requests. Authentication uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods. This API can be useful for implementing access policies based on verified agent identity or enriching your own datasets.
128
133
 
@@ -194,7 +199,7 @@ TypeScript >= 4.7 is supported.
194
199
 
195
200
  The following runtimes are supported:
196
201
 
197
- - Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs))
202
+ - Node.js 20 or later (Node.js 24 LTS recommended)
198
203
 
199
204
  ## Support
200
205
 
@@ -1,6 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from "http";
2
- import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
- import type { AgentType, IdentificationRequest, IdentificationResult, KnownAgentsOptions, TrackPageviewOrRESTCallOptions, VisitRequest } from "./types";
2
+ import type { AgentType, IdentificationRequest, IdentificationResult, KnownAgentsOptions, TrackVisitOptions } from "./types";
4
3
  /**
5
4
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
6
5
  */
@@ -19,34 +18,15 @@ export declare class KnownAgents {
19
18
  */
20
19
  constructor(accessToken: string, options?: KnownAgentsOptions);
21
20
  /**
22
- * Tracks a single non-MCP pageview or REST API call for analytics.
23
- * The visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
21
+ * Tracks a single pageview or API call for analytics.
22
+ * For Node.js responses, the visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
24
23
  * This method is non-blocking and handles upload errors internally.
25
24
  *
26
- * @param request - The incoming Node.js HTTP request.
27
- * @param response - The outgoing Node.js HTTP response.
28
- * @param options - Optional ACP or UCP response metadata for REST API calls.
29
- */
30
- trackPageviewOrRESTCall(request: IncomingMessage, response: ServerResponse, options?: TrackPageviewOrRESTCallOptions): void;
31
- /**
32
- * Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
33
- * Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
34
- * Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
35
- * This method is non-blocking and handles upload errors internally.
36
- *
37
- * @param request - The incoming Node.js HTTP request.
38
- * @param response - The outgoing Node.js HTTP response.
39
- * @param transport - The MCP streamable HTTP transport handling the request.
40
- */
41
- trackMCPCall(request: IncomingMessage, response: ServerResponse, transport: StreamableHTTPServerTransport): void;
42
- /**
43
- * Adds multiple visits to the visit event queue for analytics.
44
- * This method is non-blocking and handles upload errors internally.
45
- * All visit events are uploaded together when the queue size reaches the configured threshold or the flush interval elapses.
46
- *
47
- * @param requests - An array of visits.
25
+ * @param request - The incoming HTTP request.
26
+ * @param response - The outgoing HTTP response.
27
+ * @param options - Optional protocol metadata.
48
28
  */
49
- trackVisits(requests: VisitRequest[]): void;
29
+ trackVisit(request: IncomingMessage | Request, response?: ServerResponse | Response, options?: TrackVisitOptions): void;
50
30
  /**
51
31
  * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
52
32
  * This method handles upload errors internally.
@@ -83,6 +63,7 @@ export declare class KnownAgents {
83
63
  * @throws {Error} If the API call fails or returns a non-200 status.
84
64
  */
85
65
  identifyAgents(requests: IdentificationRequest[]): Promise<IdentificationResult[]>;
66
+ private enqueueVisit;
86
67
  private scheduleFlush;
87
68
  private sendVisits;
88
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"KnownAgents.d.ts","sourceRoot":"","sources":["../src/KnownAgents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAGvG,OAAO,KAAK,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,8BAA8B,EAC9B,YAAY,EACf,MAAM,SAAS,CAAA;AAKhB;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAQ;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,mBAAmB,CAAmC;IAE9D;;;;;OAKG;gBACS,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IAMjE;;;;;;;;OAQG;IACH,uBAAuB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,8BAA8B,GAAG,IAAI;IAoC3H;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,6BAA6B,GAAG,IAAI;IAoEhH;;;;;;OAMG;IACH,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IA2B3C;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB;;;;;;;;;OASG;IACG,iBAAiB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBzF;;;;;;;OAOG;IACG,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAqB5E;;;;;;;OAOG;IACG,cAAc,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAsBxF,OAAO,CAAC,aAAa;YAaP,UAAU;CAmB3B"}
1
+ {"version":3,"file":"KnownAgents.d.ts","sourceRoot":"","sources":["../src/KnownAgents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAI3D,OAAO,KAAK,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EAEpB,MAAM,SAAS,CAAA;AAMhB;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAQ;IACpD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,mBAAmB,CAAmC;IAE9D;;;;;OAKG;gBACS,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IAMjE;;;;;;;;OAQG;IACH,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAqDvH;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB;;;;;;;;;OASG;IACG,iBAAiB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBzF;;;;;;;OAOG;IACG,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAsB5E;;;;;;;OAOG;IACG,cAAc,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAuBxF,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,aAAa;YAaP,UAAU;CAwB3B"}
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KnownAgents = void 0;
4
+ const server_1 = require("@modelcontextprotocol/server");
4
5
  const headers_1 = require("./headers");
5
6
  const paths_1 = require("./paths");
6
7
  const DEFAULT_FLUSH_INTERVAL_IN_MILLISECONDS = 10000;
7
8
  const DEFAULT_FLUSH_QUEUE_SIZE = 1;
9
+ const NODE_PACKAGE_VERSION = "3.0.0";
8
10
  /**
9
11
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
10
12
  */
@@ -23,103 +25,51 @@ class KnownAgents {
23
25
  this.flushQueueSize = options.flushQueueSize ?? DEFAULT_FLUSH_QUEUE_SIZE;
24
26
  }
25
27
  /**
26
- * Tracks a single non-MCP pageview or REST API call for analytics.
27
- * The visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
28
+ * Tracks a single pageview or API call for analytics.
29
+ * For Node.js responses, the visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
28
30
  * This method is non-blocking and handles upload errors internally.
29
31
  *
30
- * @param request - The incoming Node.js HTTP request.
31
- * @param response - The outgoing Node.js HTTP response.
32
- * @param options - Optional ACP or UCP response metadata for REST API calls.
32
+ * @param request - The incoming HTTP request.
33
+ * @param response - The outgoing HTTP response.
34
+ * @param options - Optional protocol metadata.
33
35
  */
34
- trackPageviewOrRESTCall(request, response, options) {
35
- const isMCPCall = (0, headers_1.getIsMCPCall)(request.headers);
36
- const path = request.url;
36
+ trackVisit(request, response, options) {
37
+ const url = request instanceof Request ? new URL(request.url) : undefined;
38
+ const requestHeaders = request instanceof Request ? Object.fromEntries(request.headers.entries()) : request.headers;
39
+ const path = url ? `${url.pathname}${url.search}` : request.url;
37
40
  const method = request.method;
38
- if (isMCPCall || !path || (0, paths_1.getIsPathExcluded)(path) || !method) {
41
+ if (!path || (0, paths_1.getIsPathExcluded)(path) || !method) {
39
42
  return;
40
43
  }
41
44
  const created = new Date();
42
45
  const sendVisit = () => {
43
- const responseDurationInMilliseconds = Date.now() - created.getTime();
46
+ const responseHeaders = response instanceof Response ? Object.fromEntries(response.headers.entries()) : response?.getHeaders() ?? {};
47
+ const responseStatusCode = response instanceof Response ? response.status : response?.statusCode;
48
+ const responseDurationInMilliseconds = options?.responseDurationInMilliseconds ?? (!response || response instanceof Response ? undefined : Date.now() - created.getTime());
49
+ const mcp = options?.mcp;
50
+ const clientInfo = mcp?.context?.mcpReq.envelope?.[server_1.CLIENT_INFO_META_KEY];
44
51
  const visitRequest = {
45
52
  request_path: path,
46
53
  request_method: method,
47
- request_headers: request.headers,
48
- response_status_code: response?.statusCode,
49
- response_headers: response.getHeaders(),
54
+ request_headers: requestHeaders,
55
+ response_status_code: responseStatusCode,
56
+ response_headers: responseHeaders,
50
57
  response_duration_in_milliseconds: responseDurationInMilliseconds,
51
- acp_response_body: options?.acpResponseBody,
52
- ucp_response_body: options?.ucpResponseBody,
58
+ mcp_request_method: mcp?.context?.mcpReq.method,
59
+ mcp_request_name: mcp?.toolName,
60
+ mcp_request_client_info_name: clientInfo?.name,
61
+ mcp_request_client_info_version: clientInfo?.version,
62
+ mcp_response_result_is_error: mcp?.result?.isError,
63
+ acp_response_body: options?.acp?.responseBody,
64
+ ucp_response_body: options?.ucp?.responseBody,
53
65
  created: created.toISOString()
54
66
  };
55
- this.trackVisits([visitRequest]);
67
+ this.enqueueVisit(visitRequest);
56
68
  };
57
- if (response.writableEnded) {
69
+ if (!response || response instanceof Response) {
58
70
  sendVisit();
59
- }
60
- else {
61
- response.once("finish", sendVisit);
62
- }
63
- }
64
- /**
65
- * Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
66
- * Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
67
- * Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
68
- * This method is non-blocking and handles upload errors internally.
69
- *
70
- * @param request - The incoming Node.js HTTP request.
71
- * @param response - The outgoing Node.js HTTP response.
72
- * @param transport - The MCP streamable HTTP transport handling the request.
73
- */
74
- trackMCPCall(request, response, transport) {
75
- const path = request.url;
76
- const method = request.method;
77
- if (!path || (0, paths_1.getIsPathExcluded)(path) || !method) {
78
71
  return;
79
72
  }
80
- const created = new Date();
81
- let mcpRequestBody;
82
- let mcpResponseBody;
83
- const originalTransportOnMessage = transport.onmessage;
84
- transport.onmessage = (message, extra) => {
85
- mcpRequestBody = message;
86
- originalTransportOnMessage?.(message, extra);
87
- };
88
- const originalTransportSend = transport.send;
89
- transport.send = async (message, options) => {
90
- mcpResponseBody = message;
91
- await originalTransportSend.call(transport, message, options);
92
- };
93
- const restoreTransport = () => {
94
- transport.onmessage = originalTransportOnMessage;
95
- transport.send = originalTransportSend;
96
- };
97
- const sendVisit = () => {
98
- restoreTransport();
99
- if (!mcpRequestBody || !mcpResponseBody) {
100
- return;
101
- }
102
- const responseDurationInMilliseconds = Date.now() - created.getTime();
103
- const visitRequest = {
104
- request_path: path,
105
- request_method: method,
106
- request_headers: request.headers,
107
- response_status_code: response?.statusCode,
108
- response_headers: response.getHeaders(),
109
- response_duration_in_milliseconds: responseDurationInMilliseconds,
110
- mcp_request_method: mcpRequestBody.method,
111
- mcp_request_name: mcpRequestBody.params?.name,
112
- mcp_request_client_info_name: mcpRequestBody.params?.clientInfo?.name,
113
- mcp_request_client_info_version: mcpRequestBody.params?.clientInfo?.version,
114
- mcp_response_result_is_error: mcpResponseBody.result?.isError,
115
- mcp_response_error_code: mcpResponseBody.error?.code,
116
- mcp_response_error_message: mcpResponseBody.error?.message,
117
- acp_response_body: (0, headers_1.getIsACPCall)(request.headers) ? mcpResponseBody.result : undefined,
118
- ucp_response_body: (0, headers_1.getIsUCPCall)(request.headers) ? mcpResponseBody.result : undefined,
119
- created: created.toISOString()
120
- };
121
- this.trackVisits([visitRequest]);
122
- };
123
73
  if (response.writableEnded) {
124
74
  sendVisit();
125
75
  }
@@ -127,35 +77,6 @@ class KnownAgents {
127
77
  response.once("finish", sendVisit);
128
78
  }
129
79
  }
130
- /**
131
- * Adds multiple visits to the visit event queue for analytics.
132
- * This method is non-blocking and handles upload errors internally.
133
- * All visit events are uploaded together when the queue size reaches the configured threshold or the flush interval elapses.
134
- *
135
- * @param requests - An array of visits.
136
- */
137
- trackVisits(requests) {
138
- const filteredRequests = requests
139
- .filter((request) => {
140
- return !(0, paths_1.getIsPathExcluded)(request.request_path);
141
- })
142
- .map((request) => {
143
- return {
144
- ...request,
145
- request_headers: (0, headers_1.getFilteredHeaders)(request.request_headers),
146
- response_headers: request.response_headers ? (0, headers_1.getFilteredHeaders)(request.response_headers) : undefined,
147
- };
148
- });
149
- if (filteredRequests.length === 0) {
150
- return;
151
- }
152
- this.visitQueue.push(...filteredRequests);
153
- if (this.visitQueue.length >= this.flushQueueSize) {
154
- void this.flush();
155
- return;
156
- }
157
- this.scheduleFlush();
158
- }
159
80
  /**
160
81
  * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
161
82
  * This method handles upload errors internally.
@@ -195,7 +116,8 @@ class KnownAgents {
195
116
  },
196
117
  body: JSON.stringify({
197
118
  agent_types: agentTypes,
198
- disallow: disallow
119
+ disallow: disallow,
120
+ node_package_version: NODE_PACKAGE_VERSION
199
121
  })
200
122
  });
201
123
  if (response.ok) {
@@ -223,7 +145,8 @@ class KnownAgents {
223
145
  body: JSON.stringify({
224
146
  request_headers: (0, headers_1.getFilteredHeaders)(request.headers),
225
147
  request_ip: request.socket.remoteAddress,
226
- request_path: request.url
148
+ request_path: request.url,
149
+ node_package_version: NODE_PACKAGE_VERSION
227
150
  })
228
151
  });
229
152
  if (response.ok) {
@@ -252,6 +175,7 @@ class KnownAgents {
252
175
  return {
253
176
  ...request,
254
177
  request_headers: (0, headers_1.getFilteredHeaders)(request.request_headers),
178
+ node_package_version: NODE_PACKAGE_VERSION
255
179
  };
256
180
  }))
257
181
  });
@@ -262,6 +186,18 @@ class KnownAgents {
262
186
  throw new Error(`Known Agents failed to identify agents: ${response.status} ${response.statusText}`);
263
187
  }
264
188
  }
189
+ enqueueVisit(request) {
190
+ this.visitQueue.push({
191
+ ...request,
192
+ request_headers: (0, headers_1.getFilteredHeaders)(request.request_headers),
193
+ response_headers: request.response_headers ? (0, headers_1.getFilteredHeaders)(request.response_headers) : undefined,
194
+ });
195
+ if (this.visitQueue.length >= this.flushQueueSize) {
196
+ void this.flush();
197
+ return;
198
+ }
199
+ this.scheduleFlush();
200
+ }
265
201
  scheduleFlush() {
266
202
  if (this.flushTimeout) {
267
203
  return;
@@ -280,7 +216,12 @@ class KnownAgents {
280
216
  "Authorization": `Bearer ${this.accessToken}`,
281
217
  "Content-Type": "application/json"
282
218
  },
283
- body: JSON.stringify(requests)
219
+ body: JSON.stringify(requests.map((request) => {
220
+ return {
221
+ ...request,
222
+ node_package_version: NODE_PACKAGE_VERSION
223
+ };
224
+ }))
284
225
  });
285
226
  if (!response.ok) {
286
227
  throw new Error(`${response.status} ${response.statusText}`);
package/dist/headers.d.ts CHANGED
@@ -1,6 +1,3 @@
1
1
  import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http";
2
2
  export declare function getFilteredHeaders<Headers extends IncomingHttpHeaders | OutgoingHttpHeaders>(headers: Headers): Headers;
3
- export declare function getIsMCPCall(headers: IncomingHttpHeaders): boolean;
4
- export declare function getIsUCPCall(headers: IncomingHttpHeaders): boolean;
5
- export declare function getIsACPCall(headers: IncomingHttpHeaders): boolean;
6
3
  //# sourceMappingURL=headers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AA2BpE,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUvH;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE"}
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AA2BpE,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUvH"}
package/dist/headers.js CHANGED
@@ -1,9 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getFilteredHeaders = getFilteredHeaders;
4
- exports.getIsMCPCall = getIsMCPCall;
5
- exports.getIsUCPCall = getIsUCPCall;
6
- exports.getIsACPCall = getIsACPCall;
7
4
  const filteredHeaderNames = [
8
5
  "authorization",
9
6
  "proxy-authorization",
@@ -37,18 +34,3 @@ function getFilteredHeaders(headers) {
37
34
  });
38
35
  return filteredHeaders;
39
36
  }
40
- function getIsMCPCall(headers) {
41
- return Object.keys(headers).some((key) => {
42
- return key.toLowerCase() === "mcp-protocol-version";
43
- });
44
- }
45
- function getIsUCPCall(headers) {
46
- return Object.keys(headers).some((key) => {
47
- return key.toLowerCase() === "ucp-agent";
48
- });
49
- }
50
- function getIsACPCall(headers) {
51
- return Object.keys(headers).some((key) => {
52
- return key.toLowerCase() === "api-version";
53
- });
54
- }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { KnownAgents } from "./KnownAgents";
2
- export { AgentType, type IdentificationRequest, type IdentificationResult, type KnownAgentsOptions, type TrackPageviewOrRESTCallOptions, type VisitRequest } from "./types";
2
+ export { AgentType, type IdentificationRequest, type IdentificationResult, type KnownAgentsOptions, type TrackVisitOptions } from "./types";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACH,SAAS,EACT,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,8BAA8B,EACnC,KAAK,YAAY,EACpB,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACH,SAAS,EACT,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACzB,MAAM,SAAS,CAAA"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http";
2
+ import type { CallToolResult, ServerContext } from "@modelcontextprotocol/server";
2
3
  /**
3
4
  * Batching configuration for the Known Agents client.
4
5
  */
@@ -9,17 +10,24 @@ export interface KnownAgentsOptions {
9
10
  flushQueueSize?: number;
10
11
  }
11
12
  /**
12
- * Additional metadata to include when tracking a pageview or REST API call.
13
+ * Additional metadata to include when tracking a visit.
13
14
  */
14
- export interface TrackPageviewOrRESTCallOptions {
15
- /** The ACP response body. */
16
- acpResponseBody?: Record<string, unknown>;
17
- /** The UCP response body. */
18
- ucpResponseBody?: Record<string, unknown>;
15
+ export interface TrackVisitOptions {
16
+ responseDurationInMilliseconds?: number;
17
+ mcp?: {
18
+ context?: ServerContext;
19
+ result?: CallToolResult;
20
+ toolName?: string;
21
+ };
22
+ acp?: {
23
+ responseBody: Record<string, unknown>;
24
+ };
25
+ ucp?: {
26
+ responseBody: Record<string, unknown>;
27
+ };
19
28
  }
20
29
  /**
21
- * A normalized visit payload for the Known Agents API.
22
- * Use this with `trackVisits()` to manually add visit events to the queue.
30
+ * The normalized visit payload sent to the Known Agents API.
23
31
  */
24
32
  export interface VisitRequest {
25
33
  /** The URL path and query string of the request (e.g. "/about?foo=bar") */
@@ -44,10 +52,6 @@ export interface VisitRequest {
44
52
  mcp_request_client_info_version?: string;
45
53
  /** Whether the MCP response result is an error */
46
54
  mcp_response_result_is_error?: boolean;
47
- /** The MCP response error code */
48
- mcp_response_error_code?: number;
49
- /** The MCP response error message */
50
- mcp_response_error_message?: string;
51
55
  /** The ACP response body */
52
56
  acp_response_body?: Record<string, unknown>;
53
57
  /** The UCP response body */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knownagents/sdk",
3
- "version": "2.5.0",
3
+ "version": "3.0.0",
4
4
  "description": "The official Node.js SDK for Known Agents: AI agent analytics (bot traffic), LLM referral tracking, automatic robots.txt, agent identification",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -59,13 +59,13 @@
59
59
  },
60
60
  "homepage": "https://github.com/knownagents/node-sdk#readme",
61
61
  "engines": {
62
- "node": ">=18"
62
+ "node": ">=20"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/node": "^22.19.17",
66
- "typescript": "^5.8.2"
66
+ "typescript": "^6.0.3"
67
67
  },
68
68
  "dependencies": {
69
- "@modelcontextprotocol/sdk": "^1.29.0"
69
+ "@modelcontextprotocol/server": "^2.3.1"
70
70
  }
71
71
  }
@@ -1,18 +1,19 @@
1
1
  import type { IncomingMessage, ServerResponse } from "http"
2
- import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
3
- import { getFilteredHeaders, getIsACPCall, getIsMCPCall, getIsUCPCall } from "./headers"
2
+ import { CLIENT_INFO_META_KEY, type Implementation } from "@modelcontextprotocol/server"
3
+ import { getFilteredHeaders } from "./headers"
4
4
  import { getIsPathExcluded } from "./paths"
5
5
  import type {
6
6
  AgentType,
7
7
  IdentificationRequest,
8
8
  IdentificationResult,
9
9
  KnownAgentsOptions,
10
- TrackPageviewOrRESTCallOptions,
10
+ TrackVisitOptions,
11
11
  VisitRequest
12
12
  } from "./types"
13
13
 
14
14
  const DEFAULT_FLUSH_INTERVAL_IN_MILLISECONDS = 10000
15
15
  const DEFAULT_FLUSH_QUEUE_SIZE = 1
16
+ const NODE_PACKAGE_VERSION = "3.0.0"
16
17
 
17
18
  /**
18
19
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
@@ -38,121 +39,60 @@ export class KnownAgents {
38
39
  }
39
40
 
40
41
  /**
41
- * Tracks a single non-MCP pageview or REST API call for analytics.
42
- * The visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
42
+ * Tracks a single pageview or API call for analytics.
43
+ * For Node.js responses, the visit is added to the visit event queue after the response finishes so status, headers, and duration are included.
43
44
  * This method is non-blocking and handles upload errors internally.
44
45
  *
45
- * @param request - The incoming Node.js HTTP request.
46
- * @param response - The outgoing Node.js HTTP response.
47
- * @param options - Optional ACP or UCP response metadata for REST API calls.
46
+ * @param request - The incoming HTTP request.
47
+ * @param response - The outgoing HTTP response.
48
+ * @param options - Optional protocol metadata.
48
49
  */
49
- trackPageviewOrRESTCall(request: IncomingMessage, response: ServerResponse, options?: TrackPageviewOrRESTCallOptions): void {
50
- const isMCPCall = getIsMCPCall(request.headers)
51
- const path = request.url
50
+ trackVisit(request: IncomingMessage | Request, response?: ServerResponse | Response, options?: TrackVisitOptions): void {
51
+ const url = request instanceof Request ? new URL(request.url) : undefined
52
+ const requestHeaders = request instanceof Request ? Object.fromEntries(request.headers.entries()) : request.headers
53
+ const path = url ? `${url.pathname}${url.search}` : request.url
52
54
  const method = request.method
53
55
 
54
- if (isMCPCall || !path || getIsPathExcluded(path) || !method) {
56
+ if (!path || getIsPathExcluded(path) || !method) {
55
57
  return
56
58
  }
57
59
 
58
60
  const created = new Date()
59
61
 
60
62
  const sendVisit = () => {
61
- const responseDurationInMilliseconds = Date.now() - created.getTime()
63
+ const responseHeaders = response instanceof Response ? Object.fromEntries(response.headers.entries()) : response?.getHeaders() ?? {}
64
+ const responseStatusCode = response instanceof Response ? response.status : response?.statusCode
65
+ const responseDurationInMilliseconds = options?.responseDurationInMilliseconds ?? (!response || response instanceof Response ? undefined : Date.now() - created.getTime())
66
+ const mcp = options?.mcp
67
+ const clientInfo = (mcp?.context?.mcpReq.envelope as {
68
+ [CLIENT_INFO_META_KEY]?: Implementation
69
+ } | undefined)?.[CLIENT_INFO_META_KEY]
62
70
 
63
71
  const visitRequest: VisitRequest = {
64
72
  request_path: path,
65
73
  request_method: method,
66
- request_headers: request.headers,
67
- response_status_code: response?.statusCode,
68
- response_headers: response.getHeaders(),
74
+ request_headers: requestHeaders,
75
+ response_status_code: responseStatusCode,
76
+ response_headers: responseHeaders,
69
77
  response_duration_in_milliseconds: responseDurationInMilliseconds,
70
- acp_response_body: options?.acpResponseBody,
71
- ucp_response_body: options?.ucpResponseBody,
78
+ mcp_request_method: mcp?.context?.mcpReq.method,
79
+ mcp_request_name: mcp?.toolName,
80
+ mcp_request_client_info_name: clientInfo?.name,
81
+ mcp_request_client_info_version: clientInfo?.version,
82
+ mcp_response_result_is_error: mcp?.result?.isError,
83
+ acp_response_body: options?.acp?.responseBody,
84
+ ucp_response_body: options?.ucp?.responseBody,
72
85
  created: created.toISOString()
73
86
  }
74
87
 
75
- this.trackVisits([visitRequest])
88
+ this.enqueueVisit(visitRequest)
76
89
  }
77
90
 
78
- if (response.writableEnded) {
91
+ if (!response || response instanceof Response) {
79
92
  sendVisit()
80
- } else {
81
- response.once("finish", sendVisit)
82
- }
83
- }
84
-
85
- /**
86
- * Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
87
- * Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
88
- * Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
89
- * This method is non-blocking and handles upload errors internally.
90
- *
91
- * @param request - The incoming Node.js HTTP request.
92
- * @param response - The outgoing Node.js HTTP response.
93
- * @param transport - The MCP streamable HTTP transport handling the request.
94
- */
95
- trackMCPCall(request: IncomingMessage, response: ServerResponse, transport: StreamableHTTPServerTransport): void {
96
- const path = request.url
97
- const method = request.method
98
-
99
- if (!path || getIsPathExcluded(path) || !method) {
100
93
  return
101
94
  }
102
95
 
103
- const created = new Date()
104
-
105
- let mcpRequestBody: any
106
- let mcpResponseBody: any
107
-
108
- const originalTransportOnMessage = transport.onmessage
109
- transport.onmessage = (message, extra): void => {
110
- mcpRequestBody = message
111
- originalTransportOnMessage?.(message, extra)
112
- }
113
-
114
- const originalTransportSend = transport.send
115
- transport.send = async (message, options): Promise<void> => {
116
- mcpResponseBody = message
117
- await originalTransportSend.call(transport, message, options)
118
- }
119
-
120
- const restoreTransport = () => {
121
- transport.onmessage = originalTransportOnMessage
122
- transport.send = originalTransportSend
123
- }
124
-
125
- const sendVisit = () => {
126
- restoreTransport()
127
-
128
- if (!mcpRequestBody || !mcpResponseBody) {
129
- return
130
- }
131
-
132
- const responseDurationInMilliseconds = Date.now() - created.getTime()
133
-
134
- const visitRequest: VisitRequest = {
135
- request_path: path,
136
- request_method: method,
137
- request_headers: request.headers,
138
- response_status_code: response?.statusCode,
139
- response_headers: response.getHeaders(),
140
- response_duration_in_milliseconds: responseDurationInMilliseconds,
141
- mcp_request_method: mcpRequestBody.method,
142
- mcp_request_name: mcpRequestBody.params?.name,
143
- mcp_request_client_info_name: mcpRequestBody.params?.clientInfo?.name,
144
- mcp_request_client_info_version: mcpRequestBody.params?.clientInfo?.version,
145
- mcp_response_result_is_error: mcpResponseBody.result?.isError,
146
- mcp_response_error_code: mcpResponseBody.error?.code,
147
- mcp_response_error_message: mcpResponseBody.error?.message,
148
- acp_response_body: getIsACPCall(request.headers) ? mcpResponseBody.result : undefined,
149
- ucp_response_body: getIsUCPCall(request.headers) ? mcpResponseBody.result : undefined,
150
- created: created.toISOString()
151
- }
152
-
153
- this.trackVisits([visitRequest])
154
- }
155
-
156
96
  if (response.writableEnded) {
157
97
  sendVisit()
158
98
  } else {
@@ -160,40 +100,6 @@ export class KnownAgents {
160
100
  }
161
101
  }
162
102
 
163
- /**
164
- * Adds multiple visits to the visit event queue for analytics.
165
- * This method is non-blocking and handles upload errors internally.
166
- * All visit events are uploaded together when the queue size reaches the configured threshold or the flush interval elapses.
167
- *
168
- * @param requests - An array of visits.
169
- */
170
- trackVisits(requests: VisitRequest[]): void {
171
- const filteredRequests = requests
172
- .filter((request) => {
173
- return !getIsPathExcluded(request.request_path)
174
- })
175
- .map((request) => {
176
- return {
177
- ...request,
178
- request_headers: getFilteredHeaders(request.request_headers),
179
- response_headers: request.response_headers ? getFilteredHeaders(request.response_headers) : undefined,
180
- }
181
- })
182
-
183
- if (filteredRequests.length === 0) {
184
- return
185
- }
186
-
187
- this.visitQueue.push(...filteredRequests)
188
-
189
- if (this.visitQueue.length >= this.flushQueueSize) {
190
- void this.flush()
191
- return
192
- }
193
-
194
- this.scheduleFlush()
195
- }
196
-
197
103
  /**
198
104
  * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
199
105
  * This method handles upload errors internally.
@@ -238,7 +144,8 @@ export class KnownAgents {
238
144
  },
239
145
  body: JSON.stringify({
240
146
  agent_types: agentTypes,
241
- disallow: disallow
147
+ disallow: disallow,
148
+ node_package_version: NODE_PACKAGE_VERSION
242
149
  })
243
150
  })
244
151
 
@@ -267,7 +174,8 @@ export class KnownAgents {
267
174
  body: JSON.stringify({
268
175
  request_headers: getFilteredHeaders(request.headers),
269
176
  request_ip: request.socket.remoteAddress,
270
- request_path: request.url
177
+ request_path: request.url,
178
+ node_package_version: NODE_PACKAGE_VERSION
271
179
  })
272
180
  })
273
181
 
@@ -297,6 +205,7 @@ export class KnownAgents {
297
205
  return {
298
206
  ...request,
299
207
  request_headers: getFilteredHeaders(request.request_headers),
208
+ node_package_version: NODE_PACKAGE_VERSION
300
209
  }
301
210
  }))
302
211
  })
@@ -308,6 +217,21 @@ export class KnownAgents {
308
217
  }
309
218
  }
310
219
 
220
+ private enqueueVisit(request: VisitRequest): void {
221
+ this.visitQueue.push({
222
+ ...request,
223
+ request_headers: getFilteredHeaders(request.request_headers),
224
+ response_headers: request.response_headers ? getFilteredHeaders(request.response_headers) : undefined,
225
+ })
226
+
227
+ if (this.visitQueue.length >= this.flushQueueSize) {
228
+ void this.flush()
229
+ return
230
+ }
231
+
232
+ this.scheduleFlush()
233
+ }
234
+
311
235
  private scheduleFlush(): void {
312
236
  if (this.flushTimeout) {
313
237
  return
@@ -329,7 +253,12 @@ export class KnownAgents {
329
253
  "Authorization": `Bearer ${this.accessToken}`,
330
254
  "Content-Type": "application/json"
331
255
  },
332
- body: JSON.stringify(requests)
256
+ body: JSON.stringify(requests.map((request) => {
257
+ return {
258
+ ...request,
259
+ node_package_version: NODE_PACKAGE_VERSION
260
+ }
261
+ }))
333
262
  })
334
263
 
335
264
  if (!response.ok) {
package/src/headers.ts CHANGED
@@ -36,21 +36,3 @@ export function getFilteredHeaders<Headers extends IncomingHttpHeaders | Outgoin
36
36
 
37
37
  return filteredHeaders
38
38
  }
39
-
40
- export function getIsMCPCall(headers: IncomingHttpHeaders): boolean {
41
- return Object.keys(headers).some((key) => {
42
- return key.toLowerCase() === "mcp-protocol-version"
43
- })
44
- }
45
-
46
- export function getIsUCPCall(headers: IncomingHttpHeaders): boolean {
47
- return Object.keys(headers).some((key) => {
48
- return key.toLowerCase() === "ucp-agent"
49
- })
50
- }
51
-
52
- export function getIsACPCall(headers: IncomingHttpHeaders): boolean {
53
- return Object.keys(headers).some((key) => {
54
- return key.toLowerCase() === "api-version"
55
- })
56
- }
package/src/index.ts CHANGED
@@ -4,6 +4,5 @@ export {
4
4
  type IdentificationRequest,
5
5
  type IdentificationResult,
6
6
  type KnownAgentsOptions,
7
- type TrackPageviewOrRESTCallOptions,
8
- type VisitRequest
7
+ type TrackVisitOptions
9
8
  } from "./types"
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http"
2
+ import type { CallToolResult, ServerContext } from "@modelcontextprotocol/server"
2
3
 
3
4
  /**
4
5
  * Batching configuration for the Known Agents client.
@@ -11,18 +12,25 @@ export interface KnownAgentsOptions {
11
12
  }
12
13
 
13
14
  /**
14
- * Additional metadata to include when tracking a pageview or REST API call.
15
+ * Additional metadata to include when tracking a visit.
15
16
  */
16
- export interface TrackPageviewOrRESTCallOptions {
17
- /** The ACP response body. */
18
- acpResponseBody?: Record<string, unknown>
19
- /** The UCP response body. */
20
- ucpResponseBody?: Record<string, unknown>
17
+ export interface TrackVisitOptions {
18
+ responseDurationInMilliseconds?: number
19
+ mcp?: {
20
+ context?: ServerContext
21
+ result?: CallToolResult
22
+ toolName?: string
23
+ }
24
+ acp?: {
25
+ responseBody: Record<string, unknown>
26
+ }
27
+ ucp?: {
28
+ responseBody: Record<string, unknown>
29
+ }
21
30
  }
22
31
 
23
32
  /**
24
- * A normalized visit payload for the Known Agents API.
25
- * Use this with `trackVisits()` to manually add visit events to the queue.
33
+ * The normalized visit payload sent to the Known Agents API.
26
34
  */
27
35
  export interface VisitRequest {
28
36
  /** The URL path and query string of the request (e.g. "/about?foo=bar") */
@@ -47,10 +55,6 @@ export interface VisitRequest {
47
55
  mcp_request_client_info_version?: string
48
56
  /** Whether the MCP response result is an error */
49
57
  mcp_response_result_is_error?: boolean
50
- /** The MCP response error code */
51
- mcp_response_error_code?: number
52
- /** The MCP response error message */
53
- mcp_response_error_message?: string
54
58
  /** The ACP response body */
55
59
  acp_response_body?: Record<string, unknown>
56
60
  /** The UCP response body */