@knownagents/sdk 2.5.0 → 3.1.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,86 +33,90 @@ 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
+ }
77
+ })
78
+
79
+ return result
80
+ })
81
+
82
+ return server
83
+ })
74
84
  ```
75
85
 
76
- ### Agentic Commerce Interactions (Optional)
86
+ ### Agentic Commerce
77
87
 
78
- For REST implementations of ACP and UCP, include the response body in `trackPageviewOrRESTCall`.
88
+ For ACP and UCP endpoints, include the response body in `trackVisit`.
79
89
 
80
90
  ```ts
81
91
  // For ACP calls ...
82
92
 
83
- knownAgents.trackPageviewOrRESTCall(request, response, {
84
- acpResponseBody: acpResponseBody
93
+ knownAgents.trackVisit(request, response, {
94
+ acp: {
95
+ responseBody: acpResponseBody
96
+ }
85
97
  })
86
98
  ```
87
99
 
88
100
  ```ts
89
101
  // For UCP calls ...
90
102
 
91
- knownAgents.trackPageviewOrRESTCall(request, response, {
92
- ucpResponseBody: ucpResponseBody
103
+ knownAgents.trackVisit(request, response, {
104
+ ucp: {
105
+ responseBody: ucpResponseBody
106
+ }
93
107
  })
94
108
  ```
95
109
 
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))
110
+ ## Serve an Automatic Robots.txt ([Automatic Robots.txt](https://knownagents.com/products/automatic-robots-txt))
107
111
 
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.
112
+ 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
113
 
110
- 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).
114
+ 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
115
 
112
116
  ```ts
113
117
  import { AgentType } from "@knownagents/sdk"
114
118
 
115
- const robotsTxt = await knownAgents.generateRobotsTXT([
119
+ const robotsTxt = await knownAgents.generateRobotsTxt([
116
120
  AgentType.AIDataScraper,
117
121
  AgentType.AIDataProvider,
118
122
  AgentType.Scraper,
@@ -122,7 +126,7 @@ const robotsTxt = await knownAgents.generateRobotsTXT([
122
126
 
123
127
  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
128
 
125
- ## Use the Agent Identification API ([Agent Identification API](https://knownagents.com/products/agent-identification-api))
129
+ ## Identify & Verify Agents ([Agent Identification](https://knownagents.com/products/agent-identification-api))
126
130
 
127
131
  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
132
 
@@ -194,7 +198,7 @@ TypeScript >= 4.7 is supported.
194
198
 
195
199
  The following runtimes are supported:
196
200
 
197
- - Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs))
201
+ - Node.js 20 or later (Node.js 24 LTS recommended)
198
202
 
199
203
  ## Support
200
204
 
@@ -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.
@@ -64,7 +44,7 @@ export declare class KnownAgents {
64
44
  * @returns A promise that resolves to the generated robots.txt content as a string.
65
45
  * @throws {Error} If the API call fails or returns a non-200 status.
66
46
  */
67
- generateRobotsTXT(agentTypes: AgentType[], disallow?: string): Promise<string>;
47
+ generateRobotsTxt(agentTypes: AgentType[], disallow?: string): Promise<string>;
68
48
  /**
69
49
  * Identifies and verifies a single agent from an HTTP request.
70
50
  * Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
@@ -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;IAsDvH;;;;;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.1.0";
8
10
  /**
9
11
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
10
12
  */
@@ -23,103 +25,52 @@ 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) : request.headers;
39
+ const mcpRequestName = (0, headers_1.getHeaderValue)(requestHeaders, "mcp-name");
40
+ const path = url ? `${url.pathname}${url.search}` : request.url;
37
41
  const method = request.method;
38
- if (isMCPCall || !path || (0, paths_1.getIsPathExcluded)(path) || !method) {
42
+ if (!path || (0, paths_1.isPathExcluded)(path) || !method) {
39
43
  return;
40
44
  }
41
45
  const created = new Date();
42
46
  const sendVisit = () => {
43
- const responseDurationInMilliseconds = Date.now() - created.getTime();
47
+ const responseHeaders = response instanceof Response ? Object.fromEntries(response.headers.entries()) : response?.getHeaders() ?? {};
48
+ const responseStatusCode = response instanceof Response ? response.status : response?.statusCode;
49
+ const responseDurationInMilliseconds = options?.responseDurationInMilliseconds ?? (!response || response instanceof Response ? undefined : Date.now() - created.getTime());
50
+ const mcp = options?.mcp;
51
+ const clientInfo = mcp?.context?.mcpReq.envelope?.[server_1.CLIENT_INFO_META_KEY];
44
52
  const visitRequest = {
45
53
  request_path: path,
46
54
  request_method: method,
47
- request_headers: request.headers,
48
- response_status_code: response?.statusCode,
49
- response_headers: response.getHeaders(),
55
+ request_headers: requestHeaders,
56
+ response_status_code: responseStatusCode,
57
+ response_headers: responseHeaders,
50
58
  response_duration_in_milliseconds: responseDurationInMilliseconds,
51
- acp_response_body: options?.acpResponseBody,
52
- ucp_response_body: options?.ucpResponseBody,
59
+ mcp_request_method: mcp?.context?.mcpReq.method,
60
+ mcp_request_name: mcpRequestName,
61
+ mcp_request_client_info_name: clientInfo?.name,
62
+ mcp_request_client_info_version: clientInfo?.version,
63
+ mcp_response_result_is_error: mcp?.result?.isError,
64
+ acp_response_body: options?.acp?.responseBody,
65
+ ucp_response_body: options?.ucp?.responseBody,
53
66
  created: created.toISOString()
54
67
  };
55
- this.trackVisits([visitRequest]);
68
+ this.enqueueVisit(visitRequest);
56
69
  };
57
- if (response.writableEnded) {
70
+ if (!response || response instanceof Response) {
58
71
  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
72
  return;
79
73
  }
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
74
  if (response.writableEnded) {
124
75
  sendVisit();
125
76
  }
@@ -127,35 +78,6 @@ class KnownAgents {
127
78
  response.once("finish", sendVisit);
128
79
  }
129
80
  }
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
81
  /**
160
82
  * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
161
83
  * This method handles upload errors internally.
@@ -186,7 +108,7 @@ class KnownAgents {
186
108
  * @returns A promise that resolves to the generated robots.txt content as a string.
187
109
  * @throws {Error} If the API call fails or returns a non-200 status.
188
110
  */
189
- async generateRobotsTXT(agentTypes, disallow = "/") {
111
+ async generateRobotsTxt(agentTypes, disallow = "/") {
190
112
  const response = await fetch("https://api.knownagents.com/robots-txts", {
191
113
  method: "POST",
192
114
  headers: {
@@ -195,7 +117,8 @@ class KnownAgents {
195
117
  },
196
118
  body: JSON.stringify({
197
119
  agent_types: agentTypes,
198
- disallow: disallow
120
+ disallow: disallow,
121
+ node_package_version: NODE_PACKAGE_VERSION
199
122
  })
200
123
  });
201
124
  if (response.ok) {
@@ -221,9 +144,10 @@ class KnownAgents {
221
144
  "Content-Type": "application/json"
222
145
  },
223
146
  body: JSON.stringify({
224
- request_headers: (0, headers_1.getFilteredHeaders)(request.headers),
147
+ request_headers: (0, headers_1.filterHeaders)(request.headers),
225
148
  request_ip: request.socket.remoteAddress,
226
- request_path: request.url
149
+ request_path: request.url,
150
+ node_package_version: NODE_PACKAGE_VERSION
227
151
  })
228
152
  });
229
153
  if (response.ok) {
@@ -251,7 +175,8 @@ class KnownAgents {
251
175
  body: JSON.stringify(requests.map((request) => {
252
176
  return {
253
177
  ...request,
254
- request_headers: (0, headers_1.getFilteredHeaders)(request.request_headers),
178
+ request_headers: (0, headers_1.filterHeaders)(request.request_headers),
179
+ node_package_version: NODE_PACKAGE_VERSION
255
180
  };
256
181
  }))
257
182
  });
@@ -262,6 +187,18 @@ class KnownAgents {
262
187
  throw new Error(`Known Agents failed to identify agents: ${response.status} ${response.statusText}`);
263
188
  }
264
189
  }
190
+ enqueueVisit(request) {
191
+ this.visitQueue.push({
192
+ ...request,
193
+ request_headers: (0, headers_1.filterHeaders)(request.request_headers),
194
+ response_headers: request.response_headers ? (0, headers_1.filterHeaders)(request.response_headers) : undefined,
195
+ });
196
+ if (this.visitQueue.length >= this.flushQueueSize) {
197
+ void this.flush();
198
+ return;
199
+ }
200
+ this.scheduleFlush();
201
+ }
265
202
  scheduleFlush() {
266
203
  if (this.flushTimeout) {
267
204
  return;
@@ -280,7 +217,12 @@ class KnownAgents {
280
217
  "Authorization": `Bearer ${this.accessToken}`,
281
218
  "Content-Type": "application/json"
282
219
  },
283
- body: JSON.stringify(requests)
220
+ body: JSON.stringify(requests.map((request) => {
221
+ return {
222
+ ...request,
223
+ node_package_version: NODE_PACKAGE_VERSION
224
+ };
225
+ }))
284
226
  });
285
227
  if (!response.ok) {
286
228
  throw new Error(`${response.status} ${response.statusText}`);
package/dist/headers.d.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http";
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;
2
+ export declare function getHeaderValue(headers: IncomingHttpHeaders | OutgoingHttpHeaders, headerName: string): string | undefined;
3
+ export declare function filterHeaders<Headers extends IncomingHttpHeaders | OutgoingHttpHeaders>(headers: Headers): Headers;
6
4
  //# 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,cAAc,CAAC,OAAO,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiBzH;AAED,wBAAgB,aAAa,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUlH"}
package/dist/headers.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getFilteredHeaders = getFilteredHeaders;
4
- exports.getIsMCPCall = getIsMCPCall;
5
- exports.getIsUCPCall = getIsUCPCall;
6
- exports.getIsACPCall = getIsACPCall;
3
+ exports.getHeaderValue = getHeaderValue;
4
+ exports.filterHeaders = filterHeaders;
7
5
  const filteredHeaderNames = [
8
6
  "authorization",
9
7
  "proxy-authorization",
@@ -28,7 +26,21 @@ const filteredHeaderNames = [
28
26
  "client-secret",
29
27
  "private-token"
30
28
  ];
31
- function getFilteredHeaders(headers) {
29
+ function getHeaderValue(headers, headerName) {
30
+ const normalizedHeaderName = headerName.toLowerCase();
31
+ const matchingHeaderName = Object.keys(headers).find((candidate) => {
32
+ return candidate.toLowerCase() === normalizedHeaderName;
33
+ });
34
+ if (!matchingHeaderName) {
35
+ return undefined;
36
+ }
37
+ const headerValue = headers[matchingHeaderName];
38
+ if (Array.isArray(headerValue)) {
39
+ return headerValue[0];
40
+ }
41
+ return headerValue?.toString();
42
+ }
43
+ function filterHeaders(headers) {
32
44
  const filteredHeaders = { ...headers };
33
45
  Object.keys(filteredHeaders).forEach((key) => {
34
46
  if (filteredHeaderNames.includes(key.toLowerCase())) {
@@ -37,18 +49,3 @@ function getFilteredHeaders(headers) {
37
49
  });
38
50
  return filteredHeaders;
39
51
  }
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/paths.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare function getIsPathExcluded(path: string): boolean;
1
+ export declare function isPathExcluded(path: string): boolean;
2
2
  //# sourceMappingURL=paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuEvD"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuEpD"}
package/dist/paths.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getIsPathExcluded = getIsPathExcluded;
4
- function getIsPathExcluded(path) {
3
+ exports.isPathExcluded = isPathExcluded;
4
+ function isPathExcluded(path) {
5
5
  const pathWithoutQueryString = path.split(/[?#]/)[0];
6
6
  const lowercasedPath = pathWithoutQueryString.toLowerCase();
7
7
  const excludedPrefixes = [
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,23 @@ 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
+ };
21
+ acp?: {
22
+ responseBody: Record<string, unknown>;
23
+ };
24
+ ucp?: {
25
+ responseBody: Record<string, unknown>;
26
+ };
19
27
  }
20
28
  /**
21
- * A normalized visit payload for the Known Agents API.
22
- * Use this with `trackVisits()` to manually add visit events to the queue.
29
+ * The normalized visit payload sent to the Known Agents API.
23
30
  */
24
31
  export interface VisitRequest {
25
32
  /** The URL path and query string of the request (e.g. "/about?foo=bar") */
@@ -44,10 +51,6 @@ export interface VisitRequest {
44
51
  mcp_request_client_info_version?: string;
45
52
  /** Whether the MCP response result is an error */
46
53
  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
54
  /** The ACP response body */
52
55
  acp_response_body?: Record<string, unknown>;
53
56
  /** 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.1.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"
4
- import { getIsPathExcluded } from "./paths"
2
+ import { CLIENT_INFO_META_KEY, type Implementation } from "@modelcontextprotocol/server"
3
+ import { filterHeaders, getHeaderValue } from "./headers"
4
+ import { isPathExcluded } 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.1.0"
16
17
 
17
18
  /**
18
19
  * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
@@ -38,121 +39,61 @@ 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) : request.headers
53
+ const mcpRequestName = getHeaderValue(requestHeaders, "mcp-name")
54
+ const path = url ? `${url.pathname}${url.search}` : request.url
52
55
  const method = request.method
53
56
 
54
- if (isMCPCall || !path || getIsPathExcluded(path) || !method) {
57
+ if (!path || isPathExcluded(path) || !method) {
55
58
  return
56
59
  }
57
60
 
58
61
  const created = new Date()
59
62
 
60
63
  const sendVisit = () => {
61
- const responseDurationInMilliseconds = Date.now() - created.getTime()
64
+ const responseHeaders = response instanceof Response ? Object.fromEntries(response.headers.entries()) : response?.getHeaders() ?? {}
65
+ const responseStatusCode = response instanceof Response ? response.status : response?.statusCode
66
+ const responseDurationInMilliseconds = options?.responseDurationInMilliseconds ?? (!response || response instanceof Response ? undefined : Date.now() - created.getTime())
67
+ const mcp = options?.mcp
68
+ const clientInfo = (mcp?.context?.mcpReq.envelope as {
69
+ [CLIENT_INFO_META_KEY]?: Implementation
70
+ } | undefined)?.[CLIENT_INFO_META_KEY]
62
71
 
63
72
  const visitRequest: VisitRequest = {
64
73
  request_path: path,
65
74
  request_method: method,
66
- request_headers: request.headers,
67
- response_status_code: response?.statusCode,
68
- response_headers: response.getHeaders(),
75
+ request_headers: requestHeaders,
76
+ response_status_code: responseStatusCode,
77
+ response_headers: responseHeaders,
69
78
  response_duration_in_milliseconds: responseDurationInMilliseconds,
70
- acp_response_body: options?.acpResponseBody,
71
- ucp_response_body: options?.ucpResponseBody,
79
+ mcp_request_method: mcp?.context?.mcpReq.method,
80
+ mcp_request_name: mcpRequestName,
81
+ mcp_request_client_info_name: clientInfo?.name,
82
+ mcp_request_client_info_version: clientInfo?.version,
83
+ mcp_response_result_is_error: mcp?.result?.isError,
84
+ acp_response_body: options?.acp?.responseBody,
85
+ ucp_response_body: options?.ucp?.responseBody,
72
86
  created: created.toISOString()
73
87
  }
74
88
 
75
- this.trackVisits([visitRequest])
89
+ this.enqueueVisit(visitRequest)
76
90
  }
77
91
 
78
- if (response.writableEnded) {
92
+ if (!response || response instanceof Response) {
79
93
  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
94
  return
101
95
  }
102
96
 
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
97
  if (response.writableEnded) {
157
98
  sendVisit()
158
99
  } else {
@@ -160,40 +101,6 @@ export class KnownAgents {
160
101
  }
161
102
  }
162
103
 
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
104
  /**
198
105
  * Uploads all events currently in the visit event queue and waits for pending uploads to finish.
199
106
  * This method handles upload errors internally.
@@ -229,7 +136,7 @@ export class KnownAgents {
229
136
  * @returns A promise that resolves to the generated robots.txt content as a string.
230
137
  * @throws {Error} If the API call fails or returns a non-200 status.
231
138
  */
232
- async generateRobotsTXT(agentTypes: AgentType[], disallow: string = "/"): Promise<string> {
139
+ async generateRobotsTxt(agentTypes: AgentType[], disallow: string = "/"): Promise<string> {
233
140
  const response = await fetch("https://api.knownagents.com/robots-txts", {
234
141
  method: "POST",
235
142
  headers: {
@@ -238,7 +145,8 @@ export class KnownAgents {
238
145
  },
239
146
  body: JSON.stringify({
240
147
  agent_types: agentTypes,
241
- disallow: disallow
148
+ disallow: disallow,
149
+ node_package_version: NODE_PACKAGE_VERSION
242
150
  })
243
151
  })
244
152
 
@@ -265,9 +173,10 @@ export class KnownAgents {
265
173
  "Content-Type": "application/json"
266
174
  },
267
175
  body: JSON.stringify({
268
- request_headers: getFilteredHeaders(request.headers),
176
+ request_headers: filterHeaders(request.headers),
269
177
  request_ip: request.socket.remoteAddress,
270
- request_path: request.url
178
+ request_path: request.url,
179
+ node_package_version: NODE_PACKAGE_VERSION
271
180
  })
272
181
  })
273
182
 
@@ -296,7 +205,8 @@ export class KnownAgents {
296
205
  body: JSON.stringify(requests.map((request) => {
297
206
  return {
298
207
  ...request,
299
- request_headers: getFilteredHeaders(request.request_headers),
208
+ request_headers: filterHeaders(request.request_headers),
209
+ node_package_version: NODE_PACKAGE_VERSION
300
210
  }
301
211
  }))
302
212
  })
@@ -308,6 +218,21 @@ export class KnownAgents {
308
218
  }
309
219
  }
310
220
 
221
+ private enqueueVisit(request: VisitRequest): void {
222
+ this.visitQueue.push({
223
+ ...request,
224
+ request_headers: filterHeaders(request.request_headers),
225
+ response_headers: request.response_headers ? filterHeaders(request.response_headers) : undefined,
226
+ })
227
+
228
+ if (this.visitQueue.length >= this.flushQueueSize) {
229
+ void this.flush()
230
+ return
231
+ }
232
+
233
+ this.scheduleFlush()
234
+ }
235
+
311
236
  private scheduleFlush(): void {
312
237
  if (this.flushTimeout) {
313
238
  return
@@ -329,7 +254,12 @@ export class KnownAgents {
329
254
  "Authorization": `Bearer ${this.accessToken}`,
330
255
  "Content-Type": "application/json"
331
256
  },
332
- body: JSON.stringify(requests)
257
+ body: JSON.stringify(requests.map((request) => {
258
+ return {
259
+ ...request,
260
+ node_package_version: NODE_PACKAGE_VERSION
261
+ }
262
+ }))
333
263
  })
334
264
 
335
265
  if (!response.ok) {
package/src/headers.ts CHANGED
@@ -25,7 +25,26 @@ const filteredHeaderNames = [
25
25
  "private-token"
26
26
  ]
27
27
 
28
- export function getFilteredHeaders<Headers extends IncomingHttpHeaders | OutgoingHttpHeaders>(headers: Headers): Headers {
28
+ export function getHeaderValue(headers: IncomingHttpHeaders | OutgoingHttpHeaders, headerName: string): string | undefined {
29
+ const normalizedHeaderName = headerName.toLowerCase()
30
+ const matchingHeaderName = Object.keys(headers).find((candidate) => {
31
+ return candidate.toLowerCase() === normalizedHeaderName
32
+ })
33
+
34
+ if (!matchingHeaderName) {
35
+ return undefined
36
+ }
37
+
38
+ const headerValue = headers[matchingHeaderName]
39
+
40
+ if (Array.isArray(headerValue)) {
41
+ return headerValue[0]
42
+ }
43
+
44
+ return headerValue?.toString()
45
+ }
46
+
47
+ export function filterHeaders<Headers extends IncomingHttpHeaders | OutgoingHttpHeaders>(headers: Headers): Headers {
29
48
  const filteredHeaders = { ...headers }
30
49
 
31
50
  Object.keys(filteredHeaders).forEach((key) => {
@@ -36,21 +55,3 @@ export function getFilteredHeaders<Headers extends IncomingHttpHeaders | Outgoin
36
55
 
37
56
  return filteredHeaders
38
57
  }
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/paths.ts CHANGED
@@ -1,4 +1,4 @@
1
- export function getIsPathExcluded(path: string): boolean {
1
+ export function isPathExcluded(path: string): boolean {
2
2
  const pathWithoutQueryString = path.split(/[?#]/)[0]
3
3
  const lowercasedPath = pathWithoutQueryString.toLowerCase()
4
4
 
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,24 @@ 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
+ }
23
+ acp?: {
24
+ responseBody: Record<string, unknown>
25
+ }
26
+ ucp?: {
27
+ responseBody: Record<string, unknown>
28
+ }
21
29
  }
22
30
 
23
31
  /**
24
- * A normalized visit payload for the Known Agents API.
25
- * Use this with `trackVisits()` to manually add visit events to the queue.
32
+ * The normalized visit payload sent to the Known Agents API.
26
33
  */
27
34
  export interface VisitRequest {
28
35
  /** The URL path and query string of the request (e.g. "/about?foo=bar") */
@@ -47,10 +54,6 @@ export interface VisitRequest {
47
54
  mcp_request_client_info_version?: string
48
55
  /** Whether the MCP response result is an error */
49
56
  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
57
  /** The ACP response body */
55
58
  acp_response_body?: Record<string, unknown>
56
59
  /** The UCP response body */