@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 +53 -49
- package/dist/KnownAgents.d.ts +9 -28
- package/dist/KnownAgents.d.ts.map +1 -1
- package/dist/KnownAgents.js +56 -114
- package/dist/headers.d.ts +2 -4
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +17 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/paths.d.ts +1 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +2 -2
- package/dist/types.d.ts +15 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/KnownAgents.ts +64 -134
- package/src/headers.ts +20 -19
- package/src/index.ts +1 -2
- package/src/paths.ts +1 -1
- package/src/types.ts +15 -12
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
|
-
|
|
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
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return server
|
|
83
|
+
})
|
|
74
84
|
```
|
|
75
85
|
|
|
76
|
-
### Agentic Commerce
|
|
86
|
+
### Agentic Commerce
|
|
77
87
|
|
|
78
|
-
For
|
|
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.
|
|
84
|
-
|
|
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.
|
|
92
|
-
|
|
103
|
+
knownAgents.trackVisit(request, response, {
|
|
104
|
+
ucp: {
|
|
105
|
+
responseBody: ucpResponseBody
|
|
106
|
+
}
|
|
93
107
|
})
|
|
94
108
|
```
|
|
95
109
|
|
|
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))
|
|
110
|
+
## Serve an Automatic Robots.txt ([Automatic Robots.txt](https://knownagents.com/products/automatic-robots-txt))
|
|
107
111
|
|
|
108
|
-
|
|
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 `
|
|
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.
|
|
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
|
-
##
|
|
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
|
|
201
|
+
- Node.js 20 or later (Node.js 24 LTS recommended)
|
|
198
202
|
|
|
199
203
|
## Support
|
|
200
204
|
|
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.
|
|
@@ -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
|
-
|
|
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;
|
|
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"}
|
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.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
|
|
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) : 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 (
|
|
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
|
|
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:
|
|
48
|
-
response_status_code:
|
|
49
|
-
response_headers:
|
|
55
|
+
request_headers: requestHeaders,
|
|
56
|
+
response_status_code: responseStatusCode,
|
|
57
|
+
response_headers: responseHeaders,
|
|
50
58
|
response_duration_in_milliseconds: responseDurationInMilliseconds,
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
68
|
+
this.enqueueVisit(visitRequest);
|
|
56
69
|
};
|
|
57
|
-
if (response
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
3
|
-
export declare function
|
|
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
|
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,
|
|
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.
|
|
4
|
-
exports.
|
|
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
|
|
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
|
|
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/paths.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function isPathExcluded(path: string): boolean;
|
|
2
2
|
//# sourceMappingURL=paths.d.ts.map
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,wBAAgB,
|
|
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.
|
|
4
|
-
function
|
|
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
|
|
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
|
+
};
|
|
21
|
+
acp?: {
|
|
22
|
+
responseBody: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
ucp?: {
|
|
25
|
+
responseBody: Record<string, unknown>;
|
|
26
|
+
};
|
|
19
27
|
}
|
|
20
28
|
/**
|
|
21
|
-
*
|
|
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 */
|
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.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": ">=
|
|
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 {
|
|
4
|
-
import {
|
|
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
|
-
|
|
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
|
|
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) : 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 (
|
|
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
|
|
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:
|
|
67
|
-
response_status_code:
|
|
68
|
-
response_headers:
|
|
75
|
+
request_headers: requestHeaders,
|
|
76
|
+
response_status_code: responseStatusCode,
|
|
77
|
+
response_headers: responseHeaders,
|
|
69
78
|
response_duration_in_milliseconds: responseDurationInMilliseconds,
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
89
|
+
this.enqueueVisit(visitRequest)
|
|
76
90
|
}
|
|
77
91
|
|
|
78
|
-
if (response
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
package/src/paths.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,24 @@ 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
|
+
}
|
|
23
|
+
acp?: {
|
|
24
|
+
responseBody: Record<string, unknown>
|
|
25
|
+
}
|
|
26
|
+
ucp?: {
|
|
27
|
+
responseBody: Record<string, unknown>
|
|
28
|
+
}
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
/**
|
|
24
|
-
*
|
|
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 */
|