@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 +52 -47
- package/dist/KnownAgents.d.ts +8 -27
- package/dist/KnownAgents.d.ts.map +1 -1
- package/dist/KnownAgents.js +52 -111
- package/dist/headers.d.ts +0 -3
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +0 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +16 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/KnownAgents.ts +59 -130
- package/src/headers.ts +0 -18
- package/src/index.ts +1 -2
- package/src/types.ts +16 -12
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
|
-
|
|
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.
|
|
39
|
+
await knownAgents.flush()
|
|
46
40
|
```
|
|
47
41
|
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
54
|
+
### MCP
|
|
70
55
|
|
|
71
|
-
|
|
56
|
+
For MCP endpoints, call `trackVisit` from each tool handler.
|
|
72
57
|
|
|
73
|
-
|
|
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
|
|
87
|
+
### Agentic Commerce
|
|
77
88
|
|
|
78
|
-
For
|
|
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.
|
|
84
|
-
|
|
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.
|
|
92
|
-
|
|
104
|
+
knownAgents.trackVisit(request, response, {
|
|
105
|
+
ucp: {
|
|
106
|
+
responseBody: ucpResponseBody
|
|
107
|
+
}
|
|
93
108
|
})
|
|
94
109
|
```
|
|
95
110
|
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
202
|
+
- Node.js 20 or later (Node.js 24 LTS recommended)
|
|
198
203
|
|
|
199
204
|
## Support
|
|
200
205
|
|
package/dist/KnownAgents.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "http";
|
|
2
|
-
import type {
|
|
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
|
|
23
|
-
*
|
|
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
|
|
27
|
-
* @param response - The outgoing
|
|
28
|
-
* @param options - Optional
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/dist/KnownAgents.js
CHANGED
|
@@ -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
|
|
27
|
-
*
|
|
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
|
|
31
|
-
* @param response - The outgoing
|
|
32
|
-
* @param options - Optional
|
|
32
|
+
* @param request - The incoming HTTP request.
|
|
33
|
+
* @param response - The outgoing HTTP response.
|
|
34
|
+
* @param options - Optional protocol metadata.
|
|
33
35
|
*/
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
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 (
|
|
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
|
|
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:
|
|
48
|
-
response_status_code:
|
|
49
|
-
response_headers:
|
|
54
|
+
request_headers: requestHeaders,
|
|
55
|
+
response_status_code: responseStatusCode,
|
|
56
|
+
response_headers: responseHeaders,
|
|
50
57
|
response_duration_in_milliseconds: responseDurationInMilliseconds,
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
67
|
+
this.enqueueVisit(visitRequest);
|
|
56
68
|
};
|
|
57
|
-
if (response
|
|
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
|
package/dist/headers.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
2
|
+
export { AgentType, type IdentificationRequest, type IdentificationResult, type KnownAgentsOptions, type TrackVisitOptions } from "./types";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
13
|
+
* Additional metadata to include when tracking a visit.
|
|
13
14
|
*/
|
|
14
|
-
export interface
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
*
|
|
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 */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;
|
|
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": "
|
|
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": ">=
|
|
62
|
+
"node": ">=20"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/node": "^22.19.17",
|
|
66
|
-
"typescript": "^
|
|
66
|
+
"typescript": "^6.0.3"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@modelcontextprotocol/
|
|
69
|
+
"@modelcontextprotocol/server": "^2.3.1"
|
|
70
70
|
}
|
|
71
71
|
}
|
package/src/KnownAgents.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "http"
|
|
2
|
-
import type
|
|
3
|
-
import { getFilteredHeaders
|
|
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
|
-
|
|
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
|
|
42
|
-
*
|
|
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
|
|
46
|
-
* @param response - The outgoing
|
|
47
|
-
* @param options - Optional
|
|
46
|
+
* @param request - The incoming HTTP request.
|
|
47
|
+
* @param response - The outgoing HTTP response.
|
|
48
|
+
* @param options - Optional protocol metadata.
|
|
48
49
|
*/
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
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 (
|
|
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
|
|
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:
|
|
67
|
-
response_status_code:
|
|
68
|
-
response_headers:
|
|
74
|
+
request_headers: requestHeaders,
|
|
75
|
+
response_status_code: responseStatusCode,
|
|
76
|
+
response_headers: responseHeaders,
|
|
69
77
|
response_duration_in_milliseconds: responseDurationInMilliseconds,
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
88
|
+
this.enqueueVisit(visitRequest)
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
if (response
|
|
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
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
|
|
15
|
+
* Additional metadata to include when tracking a visit.
|
|
15
16
|
*/
|
|
16
|
-
export interface
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
*
|
|
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 */
|