@knownagents/sdk 1.10.0 → 2.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 +82 -43
- package/dist/KnownAgents.d.ts +74 -0
- package/dist/KnownAgents.d.ts.map +1 -0
- package/dist/KnownAgents.js +247 -0
- package/dist/commerce.d.ts +28 -0
- package/dist/commerce.d.ts.map +1 -0
- package/dist/commerce.js +51 -0
- package/dist/headers.d.ts +6 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +54 -0
- package/dist/index.d.ts +2 -153
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -171
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +72 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +25 -0
- package/package.json +14 -1
- package/src/KnownAgents.ts +324 -0
- package/src/commerce.ts +68 -0
- package/src/headers.ts +56 -0
- package/src/index.ts +10 -263
- package/src/paths.ts +72 -0
- package/src/types.ts +139 -0
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.org/package/@knownagents/sdk)
|
|
4
4
|
|
|
5
|
-
This library provides convenient access to [Known Agents](https://knownagents.com/) from server-side TypeScript or JavaScript.
|
|
5
|
+
This library provides convenient access to [Known Agents](https://knownagents.com/) from server-side Node.js applications written in TypeScript or JavaScript.
|
|
6
6
|
|
|
7
7
|
## Install the Package
|
|
8
8
|
|
|
@@ -24,25 +24,35 @@ const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN")
|
|
|
24
24
|
|
|
25
25
|
## How To Set Up Agent & LLM Analytics ([Full Docs](https://knownagents.com/docs/analytics))
|
|
26
26
|
|
|
27
|
-
Get realtime insight into the hidden ecosystem of [
|
|
27
|
+
Get realtime insight into the hidden ecosystem of [AI agents and other bots](https://knownagents.com/agents) browsing your website, REST APIs, and [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) servers. Measure human traffic coming from AI chat and search platforms like ChatGPT, Perplexity, and Gemini.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
### Track Pageview and REST Calls
|
|
30
|
+
|
|
31
|
+
To collect pageview and REST API analytics, call `trackPageviewOrRESTCall` with the incoming Node.js request and response.
|
|
30
32
|
|
|
31
33
|
```ts
|
|
32
|
-
knownAgents.
|
|
34
|
+
knownAgents.trackPageviewOrRESTCall(request, response)
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
For
|
|
37
|
+
For REST APIs that return [ACP (Agentic Commerce Protocol)](https://www.agenticcommerce.dev/) or [UCP (Universal Commerce Protocol)](https://ucp.dev/) commerce metadata, include the response body:
|
|
36
38
|
|
|
37
39
|
```ts
|
|
38
|
-
knownAgents.
|
|
40
|
+
knownAgents.trackPageviewOrRESTCall(request, response, {
|
|
41
|
+
restACPResponseBody: acpResponseBody
|
|
42
|
+
})
|
|
39
43
|
```
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
```ts
|
|
46
|
+
knownAgents.trackPageviewOrRESTCall(request, response, {
|
|
47
|
+
restUCPResponseBody: ucpResponseBody
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The SDK waits for the response to finish, then sends status, headers, duration, and any commerce metadata. Sensitive headers are filtered automatically.
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
#### Use Middleware if Possible
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
If your framework supports middleware, call `trackPageviewOrRESTCall` there so pageviews and REST calls are tracked from a single place. [MCP](https://modelcontextprotocol.io/) requests are skipped automatically.
|
|
46
56
|
|
|
47
57
|
```ts
|
|
48
58
|
import express from "express"
|
|
@@ -51,46 +61,75 @@ import { KnownAgents } from "@knownagents/sdk"
|
|
|
51
61
|
const app = express()
|
|
52
62
|
const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN")
|
|
53
63
|
|
|
54
|
-
app.use((
|
|
55
|
-
|
|
56
|
-
res.on('finish', () => {
|
|
57
|
-
const duration = Date.now() - start
|
|
58
|
-
knownAgents.trackVisit(req, res, duration)
|
|
59
|
-
})
|
|
60
|
-
|
|
64
|
+
app.use((request, response, next) => {
|
|
65
|
+
knownAgents.trackPageviewOrRESTCall(request, response)
|
|
61
66
|
next()
|
|
62
67
|
})
|
|
63
68
|
|
|
64
|
-
app.get("/", (
|
|
65
|
-
|
|
69
|
+
app.get("/", (request, response) => {
|
|
70
|
+
response.send("Hello, world!")
|
|
66
71
|
})
|
|
67
72
|
|
|
68
73
|
app.listen(3000, () => console.log("Server running on port 3000"))
|
|
69
74
|
```
|
|
70
75
|
|
|
71
|
-
###
|
|
76
|
+
### Track MCP Calls
|
|
72
77
|
|
|
73
|
-
For
|
|
78
|
+
For [MCP](https://modelcontextprotocol.io/) servers that use `StreamableHTTPServerTransport`, call `trackMCPCall` after connecting the transport and before the transport handles the request.
|
|
79
|
+
|
|
80
|
+
For MCP calls that return [ACP](https://www.agenticcommerce.dev/) or [UCP](https://ucp.dev/) commerce metadata, the SDK captures the relevant currency and total amount automatically.
|
|
81
|
+
|
|
82
|
+
Use this order in your MCP HTTP handler:
|
|
74
83
|
|
|
75
84
|
```ts
|
|
76
|
-
import {
|
|
85
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
|
|
86
|
+
|
|
87
|
+
const transport = new StreamableHTTPServerTransport({
|
|
88
|
+
sessionIdGenerator: undefined
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
await mcpServer.connect(transport)
|
|
92
|
+
knownAgents.trackMCPCall(request, response, transport)
|
|
93
|
+
await transport.handleRequest(request, response, parsedBody)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Use `trackVisits` instead if the same transport instance handles concurrent requests.
|
|
77
97
|
|
|
98
|
+
### Batching
|
|
99
|
+
|
|
100
|
+
For high-traffic websites, batch multiple visits together and send them periodically (e.g. every 30 seconds) using `trackVisits`. `trackVisits` can also batch MCP visits; if you do that, include the MCP fields in each `VisitRequest` and do not also call `trackMCPCall` for the same requests. Here's an Express example:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import express from "express"
|
|
104
|
+
import { KnownAgents, type VisitRequest } from "@knownagents/sdk"
|
|
105
|
+
|
|
106
|
+
const app = express()
|
|
107
|
+
const knownAgents = new KnownAgents("YOUR_ACCESS_TOKEN")
|
|
78
108
|
const visits: VisitRequest[] = []
|
|
79
109
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
app.use((request, response, next) => {
|
|
111
|
+
const created = new Date()
|
|
112
|
+
|
|
113
|
+
response.once("finish", () => {
|
|
114
|
+
visits.push({
|
|
115
|
+
request_path: request.url,
|
|
116
|
+
request_method: request.method,
|
|
117
|
+
request_headers: request.headers,
|
|
118
|
+
response_status_code: response.statusCode,
|
|
119
|
+
response_headers: response.getHeaders(),
|
|
120
|
+
response_duration_in_milliseconds: Date.now() - created.getTime(),
|
|
121
|
+
created: created.toISOString()
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
next()
|
|
88
126
|
})
|
|
89
127
|
|
|
90
|
-
// Send batch periodically
|
|
91
128
|
setInterval(() => {
|
|
92
|
-
|
|
93
|
-
|
|
129
|
+
const batch = visits.splice(0)
|
|
130
|
+
|
|
131
|
+
if (batch.length > 0) {
|
|
132
|
+
knownAgents.trackVisits(batch)
|
|
94
133
|
}
|
|
95
134
|
}, 30000)
|
|
96
135
|
```
|
|
@@ -110,21 +149,21 @@ Protect sensitive content from unwanted access and scraping. Generate a continuo
|
|
|
110
149
|
Use the `generateRobotsTXT` function. Select which `AgentType`s you want to block, and a string specifying which URLs are disallowed (e.g. `"/"` to disallow all paths).
|
|
111
150
|
|
|
112
151
|
```ts
|
|
152
|
+
import { AgentType } from "@knownagents/sdk"
|
|
153
|
+
|
|
113
154
|
const robotsTxt = await knownAgents.generateRobotsTXT([
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// ...
|
|
155
|
+
AgentType.AIDataScraper,
|
|
156
|
+
AgentType.AIDataProvider,
|
|
157
|
+
AgentType.Scraper,
|
|
158
|
+
AgentType.SEOCrawler
|
|
119
159
|
], "/")
|
|
120
|
-
|
|
121
160
|
```
|
|
122
161
|
|
|
123
162
|
The return value is a plain text robots.txt string. Generate a `robotsTxt` periodically (e.g. once per day), then cache and serve it from your website's `/robots.txt` endpoint.
|
|
124
163
|
|
|
125
164
|
## How To Use Agent Identification ([Full Docs](https://knownagents.com/docs/identification))
|
|
126
165
|
|
|
127
|
-
Use the `identifyAgent` and `identifyAgents` functions to [
|
|
166
|
+
Use the `identifyAgent` and `identifyAgents` functions to identify and verify [AI agents and bots](https://knownagents.com/agents) from network requests using Web Bot Auth (HTTP message signatures), IP matching, or other available methods. This can be useful for implementing access policies based on verified agent identity or enriching your own datasets.
|
|
128
167
|
|
|
129
168
|
### Identify a Single Request
|
|
130
169
|
|
|
@@ -134,9 +173,9 @@ Call `identifyAgent` with the incoming request.
|
|
|
134
173
|
const identification = await knownAgents.identifyAgent(request)
|
|
135
174
|
|
|
136
175
|
if (identification.result === "verified") {
|
|
137
|
-
|
|
176
|
+
handleVerifiedAgent(identification)
|
|
138
177
|
} else if (identification.result === "verification_failed") {
|
|
139
|
-
|
|
178
|
+
handleFailedVerification(identification)
|
|
140
179
|
}
|
|
141
180
|
```
|
|
142
181
|
|
|
@@ -177,8 +216,8 @@ TypeScript >= 4.7 is supported.
|
|
|
177
216
|
|
|
178
217
|
The following runtimes are supported:
|
|
179
218
|
|
|
180
|
-
- Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs))
|
|
219
|
+
- Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs))
|
|
181
220
|
|
|
182
221
|
## Support
|
|
183
222
|
|
|
184
|
-
Please [open an issue](https://github.com/knownagents/node-sdk/issues) with questions, bugs, or suggestions.
|
|
223
|
+
Please [open an issue](https://github.com/knownagents/node-sdk/issues) with questions, bugs, or suggestions.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "http";
|
|
2
|
+
import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
import type { AgentType, IdentificationRequest, IdentificationResult, TrackPageviewOrRESTCallOptions, VisitRequest } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
|
|
6
|
+
*/
|
|
7
|
+
export declare class KnownAgents {
|
|
8
|
+
private readonly accessToken;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new instance of the Known Agents client.
|
|
11
|
+
*
|
|
12
|
+
* @param accessToken - Your project's access token.
|
|
13
|
+
*/
|
|
14
|
+
constructor(accessToken: string);
|
|
15
|
+
/**
|
|
16
|
+
* Tracks a single non-MCP pageview or REST API call for analytics.
|
|
17
|
+
* The visit is sent after the response finishes so status, headers, and duration are included.
|
|
18
|
+
* This method is non-blocking and handles errors internally.
|
|
19
|
+
*
|
|
20
|
+
* @param request - The incoming Node.js HTTP request.
|
|
21
|
+
* @param response - The outgoing Node.js HTTP response.
|
|
22
|
+
* @param options - Optional ACP or UCP response metadata for REST API calls.
|
|
23
|
+
*/
|
|
24
|
+
trackPageviewOrRESTCall(request: IncomingMessage, response: ServerResponse, options?: TrackPageviewOrRESTCallOptions): void;
|
|
25
|
+
/**
|
|
26
|
+
* Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
|
|
27
|
+
* Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
|
|
28
|
+
* Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
|
|
29
|
+
* This method is non-blocking and handles errors internally.
|
|
30
|
+
*
|
|
31
|
+
* @param request - The incoming Node.js HTTP request.
|
|
32
|
+
* @param response - The outgoing Node.js HTTP response.
|
|
33
|
+
* @param transport - The MCP streamable HTTP transport handling the request.
|
|
34
|
+
*/
|
|
35
|
+
trackMCPCall(request: IncomingMessage, response: ServerResponse, transport: StreamableHTTPServerTransport): void;
|
|
36
|
+
/**
|
|
37
|
+
* Tracks multiple visits in a batch for analytics.
|
|
38
|
+
* This method is non-blocking and handles errors internally.
|
|
39
|
+
* Recommended for high-traffic websites - batch visits and send periodically (e.g. every 30 seconds).
|
|
40
|
+
*
|
|
41
|
+
* @param requests - An array of visits.
|
|
42
|
+
*/
|
|
43
|
+
trackVisits(requests: VisitRequest[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Generates a robots.txt file that disallows the specified agent types.
|
|
46
|
+
* The generated file stays up-to-date with all current and future agents in the specified categories.
|
|
47
|
+
* Cache and serve the result at your website's `/robots.txt` endpoint.
|
|
48
|
+
*
|
|
49
|
+
* @param agentTypes - An array of agent types to disallow.
|
|
50
|
+
* @param disallow - The path to disallow (default: "/" for all paths).
|
|
51
|
+
* @returns A promise that resolves to the generated robots.txt content as a string.
|
|
52
|
+
* @throws {Error} If the API call fails or returns a non-200 status.
|
|
53
|
+
*/
|
|
54
|
+
generateRobotsTXT(agentTypes: AgentType[], disallow?: string): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Identifies and verifies a single agent from an HTTP request.
|
|
57
|
+
* Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
|
|
58
|
+
*
|
|
59
|
+
* @param request - The incoming Node.js HTTP request.
|
|
60
|
+
* @returns A promise that resolves to the identification result.
|
|
61
|
+
* @throws {Error} If the API call fails or returns a non-200 status.
|
|
62
|
+
*/
|
|
63
|
+
identifyAgent(request: IncomingMessage): Promise<IdentificationResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Identifies and verifies multiple agents from HTTP requests in a batch.
|
|
66
|
+
* Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
|
|
67
|
+
*
|
|
68
|
+
* @param requests - An array of identification requests.
|
|
69
|
+
* @returns A promise that resolves to an array of identification results.
|
|
70
|
+
* @throws {Error} If the API call fails or returns a non-200 status.
|
|
71
|
+
*/
|
|
72
|
+
identifyAgents(requests: IdentificationRequest[]): Promise<IdentificationResult[]>;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=KnownAgents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KnownAgents.d.ts","sourceRoot":"","sources":["../src/KnownAgents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAavG,OAAO,KAAK,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,8BAA8B,EAC9B,YAAY,EACf,MAAM,SAAS,CAAA;AAqChB;;GAEG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC;;;;OAIG;gBACS,WAAW,EAAE,MAAM;IAI/B;;;;;;;;OAQG;IACH,uBAAuB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,8BAA8B,GAAG,IAAI;IAsC3H;;;;;;;;;OASG;IACH,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,6BAA6B,GAAG,IAAI;IAuEhH;;;;;;OAMG;IACH,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IA6B3C;;;;;;;;;OASG;IACG,iBAAiB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,QAAQ,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBzF;;;;;;;OAOG;IACG,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAmB5E;;;;;;;OAOG;IACG,cAAc,CAAC,QAAQ,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;CAsB3F"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KnownAgents = void 0;
|
|
4
|
+
const commerce_1 = require("./commerce");
|
|
5
|
+
const headers_1 = require("./headers");
|
|
6
|
+
const paths_1 = require("./paths");
|
|
7
|
+
/**
|
|
8
|
+
* Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
|
|
9
|
+
*/
|
|
10
|
+
class KnownAgents {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new instance of the Known Agents client.
|
|
13
|
+
*
|
|
14
|
+
* @param accessToken - Your project's access token.
|
|
15
|
+
*/
|
|
16
|
+
constructor(accessToken) {
|
|
17
|
+
this.accessToken = accessToken;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Tracks a single non-MCP pageview or REST API call for analytics.
|
|
21
|
+
* The visit is sent after the response finishes so status, headers, and duration are included.
|
|
22
|
+
* This method is non-blocking and handles errors internally.
|
|
23
|
+
*
|
|
24
|
+
* @param request - The incoming Node.js HTTP request.
|
|
25
|
+
* @param response - The outgoing Node.js HTTP response.
|
|
26
|
+
* @param options - Optional ACP or UCP response metadata for REST API calls.
|
|
27
|
+
*/
|
|
28
|
+
trackPageviewOrRESTCall(request, response, options) {
|
|
29
|
+
const isMCPCall = (0, headers_1.getIsMCPCall)(request.headers);
|
|
30
|
+
const path = request.url;
|
|
31
|
+
const method = request.method;
|
|
32
|
+
if (isMCPCall || !path || (0, paths_1.getIsPathExcluded)(path) || !method) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const created = new Date();
|
|
36
|
+
const sendVisit = () => {
|
|
37
|
+
const responseDurationInMilliseconds = Date.now() - created.getTime();
|
|
38
|
+
const visitRequest = {
|
|
39
|
+
request_path: path,
|
|
40
|
+
request_method: method,
|
|
41
|
+
request_headers: request.headers,
|
|
42
|
+
response_status_code: response?.statusCode,
|
|
43
|
+
response_headers: response.getHeaders(),
|
|
44
|
+
response_duration_in_milliseconds: responseDurationInMilliseconds,
|
|
45
|
+
acp_response_currency: options?.restACPResponseBody ? (0, commerce_1.getRESTACPCurrencyIfPossible)(options?.restACPResponseBody) : undefined,
|
|
46
|
+
acp_response_total_amount: options?.restACPResponseBody ? (0, commerce_1.getRESTACPTotalAmountIfPossible)(options?.restACPResponseBody) : undefined,
|
|
47
|
+
ucp_response_currency: options?.restUCPResponseBody ? (0, commerce_1.getRESTUCPCurrencyIfPossible)(options?.restUCPResponseBody) : undefined,
|
|
48
|
+
ucp_response_total_amount: options?.restUCPResponseBody ? (0, commerce_1.getRESTUCPTotalAmountIfPossible)(options?.restUCPResponseBody) : undefined,
|
|
49
|
+
created: created.toISOString()
|
|
50
|
+
};
|
|
51
|
+
this.trackVisits([visitRequest]);
|
|
52
|
+
};
|
|
53
|
+
if (response.writableEnded) {
|
|
54
|
+
sendVisit();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
response.once("finish", sendVisit);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
|
|
62
|
+
* Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
|
|
63
|
+
* Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
|
|
64
|
+
* This method is non-blocking and handles errors internally.
|
|
65
|
+
*
|
|
66
|
+
* @param request - The incoming Node.js HTTP request.
|
|
67
|
+
* @param response - The outgoing Node.js HTTP response.
|
|
68
|
+
* @param transport - The MCP streamable HTTP transport handling the request.
|
|
69
|
+
*/
|
|
70
|
+
trackMCPCall(request, response, transport) {
|
|
71
|
+
const isMCPCall = (0, headers_1.getIsMCPCall)(request.headers);
|
|
72
|
+
const path = request.url;
|
|
73
|
+
const method = request.method;
|
|
74
|
+
if (!isMCPCall || !path || (0, paths_1.getIsPathExcluded)(path) || !method) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const created = new Date();
|
|
78
|
+
let mcpRequestBody;
|
|
79
|
+
let mcpResponseBody;
|
|
80
|
+
const originalTransportOnMessage = transport.onmessage;
|
|
81
|
+
transport.onmessage = (message, extra) => {
|
|
82
|
+
mcpRequestBody = message;
|
|
83
|
+
originalTransportOnMessage?.(message, extra);
|
|
84
|
+
};
|
|
85
|
+
const originalTransportSend = transport.send;
|
|
86
|
+
transport.send = async (message, options) => {
|
|
87
|
+
mcpResponseBody = message;
|
|
88
|
+
await originalTransportSend.call(transport, message, options);
|
|
89
|
+
};
|
|
90
|
+
const restoreTransport = () => {
|
|
91
|
+
transport.onmessage = originalTransportOnMessage;
|
|
92
|
+
transport.send = originalTransportSend;
|
|
93
|
+
};
|
|
94
|
+
const sendVisit = () => {
|
|
95
|
+
restoreTransport();
|
|
96
|
+
if (!mcpRequestBody || !mcpResponseBody) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const responseDurationInMilliseconds = Date.now() - created.getTime();
|
|
100
|
+
const visitRequest = {
|
|
101
|
+
request_path: path,
|
|
102
|
+
request_method: method,
|
|
103
|
+
request_headers: request.headers,
|
|
104
|
+
response_status_code: response?.statusCode,
|
|
105
|
+
response_headers: response.getHeaders(),
|
|
106
|
+
response_duration_in_milliseconds: responseDurationInMilliseconds,
|
|
107
|
+
mcp_request_method: mcpRequestBody.method,
|
|
108
|
+
mcp_request_tool_name: mcpRequestBody.params?.name,
|
|
109
|
+
mcp_request_client_info_name: mcpRequestBody.params?.clientInfo?.name,
|
|
110
|
+
mcp_request_client_info_version: mcpRequestBody.params?.clientInfo?.version,
|
|
111
|
+
mcp_response_result_is_error: mcpResponseBody.result?.isError,
|
|
112
|
+
mcp_response_error_code: mcpResponseBody.error?.code,
|
|
113
|
+
mcp_response_error_message: mcpResponseBody.error?.message,
|
|
114
|
+
acp_response_currency: (0, commerce_1.getMCPACPCurrencyIfPossible)(request, mcpResponseBody),
|
|
115
|
+
acp_response_total_amount: (0, commerce_1.getMCPACPTotalAmountIfPossible)(request, mcpResponseBody),
|
|
116
|
+
ucp_response_currency: (0, commerce_1.getMCPUCPCurrencyIfPossible)(request, mcpResponseBody),
|
|
117
|
+
ucp_response_total_amount: (0, commerce_1.getMCPUCPTotalAmountIfPossible)(request, mcpResponseBody),
|
|
118
|
+
created: created.toISOString()
|
|
119
|
+
};
|
|
120
|
+
this.trackVisits([visitRequest]);
|
|
121
|
+
};
|
|
122
|
+
if (response.writableEnded) {
|
|
123
|
+
sendVisit();
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
response.once("finish", sendVisit);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Tracks multiple visits in a batch for analytics.
|
|
131
|
+
* This method is non-blocking and handles errors internally.
|
|
132
|
+
* Recommended for high-traffic websites - batch visits and send periodically (e.g. every 30 seconds).
|
|
133
|
+
*
|
|
134
|
+
* @param requests - An array of visits.
|
|
135
|
+
*/
|
|
136
|
+
trackVisits(requests) {
|
|
137
|
+
const filteredRequests = requests
|
|
138
|
+
.filter((request) => {
|
|
139
|
+
return !(0, paths_1.getIsPathExcluded)(request.request_path);
|
|
140
|
+
})
|
|
141
|
+
.map((request) => {
|
|
142
|
+
return {
|
|
143
|
+
...request,
|
|
144
|
+
request_headers: (0, headers_1.getFilteredHeaders)(request.request_headers),
|
|
145
|
+
response_headers: request.response_headers ? (0, headers_1.getFilteredHeaders)(request.response_headers) : undefined,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
if (filteredRequests.length === 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
fetch("https://api.knownagents.com/visits", {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
155
|
+
"Content-Type": "application/json"
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(filteredRequests)
|
|
158
|
+
}).catch(error => {
|
|
159
|
+
console.error(`Known Agents failed to track visits: ${error.message}`);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Generates a robots.txt file that disallows the specified agent types.
|
|
164
|
+
* The generated file stays up-to-date with all current and future agents in the specified categories.
|
|
165
|
+
* Cache and serve the result at your website's `/robots.txt` endpoint.
|
|
166
|
+
*
|
|
167
|
+
* @param agentTypes - An array of agent types to disallow.
|
|
168
|
+
* @param disallow - The path to disallow (default: "/" for all paths).
|
|
169
|
+
* @returns A promise that resolves to the generated robots.txt content as a string.
|
|
170
|
+
* @throws {Error} If the API call fails or returns a non-200 status.
|
|
171
|
+
*/
|
|
172
|
+
async generateRobotsTXT(agentTypes, disallow = "/") {
|
|
173
|
+
const response = await fetch("https://api.knownagents.com/robots-txts", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
177
|
+
"Content-Type": "application/json"
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
agent_types: agentTypes,
|
|
181
|
+
disallow: disallow
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
if (response.ok) {
|
|
185
|
+
return await response.text();
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
throw new Error(`Known Agents failed to generate robots.txt: ${response.status} ${response.statusText}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Identifies and verifies a single agent from an HTTP request.
|
|
193
|
+
* Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
|
|
194
|
+
*
|
|
195
|
+
* @param request - The incoming Node.js HTTP request.
|
|
196
|
+
* @returns A promise that resolves to the identification result.
|
|
197
|
+
* @throws {Error} If the API call fails or returns a non-200 status.
|
|
198
|
+
*/
|
|
199
|
+
async identifyAgent(request) {
|
|
200
|
+
const response = await fetch("https://api.knownagents.com/agent-identifications", {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: {
|
|
203
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
204
|
+
"Content-Type": "application/json"
|
|
205
|
+
},
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
request_headers: (0, headers_1.getFilteredHeaders)(request.headers)
|
|
208
|
+
})
|
|
209
|
+
});
|
|
210
|
+
if (response.ok) {
|
|
211
|
+
return await response.json();
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
throw new Error(`Known Agents failed to identify agent: ${response.status} ${response.statusText}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Identifies and verifies multiple agents from HTTP requests in a batch.
|
|
219
|
+
* Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
|
|
220
|
+
*
|
|
221
|
+
* @param requests - An array of identification requests.
|
|
222
|
+
* @returns A promise that resolves to an array of identification results.
|
|
223
|
+
* @throws {Error} If the API call fails or returns a non-200 status.
|
|
224
|
+
*/
|
|
225
|
+
async identifyAgents(requests) {
|
|
226
|
+
const response = await fetch("https://api.knownagents.com/agent-identifications", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: {
|
|
229
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
230
|
+
"Content-Type": "application/json"
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify(requests.map(request => {
|
|
233
|
+
return {
|
|
234
|
+
...request,
|
|
235
|
+
request_headers: (0, headers_1.getFilteredHeaders)(request.request_headers),
|
|
236
|
+
};
|
|
237
|
+
}))
|
|
238
|
+
});
|
|
239
|
+
if (response.ok) {
|
|
240
|
+
return await response.json();
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
throw new Error(`Known Agents failed to identify agents: ${response.status} ${response.statusText}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
exports.KnownAgents = KnownAgents;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IncomingMessage } from "http";
|
|
2
|
+
import type { RESTACPResponseBody, RESTUCPResponseBody } from "./types";
|
|
3
|
+
interface MCPCommerceResponseBody {
|
|
4
|
+
result?: {
|
|
5
|
+
currency?: string;
|
|
6
|
+
totals?: {
|
|
7
|
+
type?: string;
|
|
8
|
+
amount?: number;
|
|
9
|
+
}[];
|
|
10
|
+
structuredContent?: {
|
|
11
|
+
currency?: string;
|
|
12
|
+
totals?: {
|
|
13
|
+
type?: string;
|
|
14
|
+
amount?: number;
|
|
15
|
+
}[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function getRESTACPCurrencyIfPossible(restACPResponseBody: RESTACPResponseBody): string | undefined;
|
|
20
|
+
export declare function getRESTACPTotalAmountIfPossible(restACPResponseBody: RESTACPResponseBody): number | undefined;
|
|
21
|
+
export declare function getMCPACPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined;
|
|
22
|
+
export declare function getMCPACPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined;
|
|
23
|
+
export declare function getRESTUCPCurrencyIfPossible(restUCPResponseBody: RESTUCPResponseBody): string | undefined;
|
|
24
|
+
export declare function getRESTUCPTotalAmountIfPossible(restUCPResponseBody: RESTUCPResponseBody): number | undefined;
|
|
25
|
+
export declare function getMCPUCPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined;
|
|
26
|
+
export declare function getMCPUCPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=commerce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commerce.d.ts","sourceRoot":"","sources":["../src/commerce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAEvE,UAAU,uBAAuB;IAC7B,MAAM,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,MAAM,CAAC,EAAE;YACL,IAAI,CAAC,EAAE,MAAM,CAAA;YACb,MAAM,CAAC,EAAE,MAAM,CAAA;SAClB,EAAE,CAAA;QACH,iBAAiB,CAAC,EAAE;YAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;YACjB,MAAM,CAAC,EAAE;gBACL,IAAI,CAAC,EAAE,MAAM,CAAA;gBACb,MAAM,CAAC,EAAE,MAAM,CAAA;aAClB,EAAE,CAAA;SACN,CAAA;KACJ,CAAA;CACJ;AAED,wBAAgB,4BAA4B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAEzG;AAED,wBAAgB,+BAA+B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAI5G;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAGlI;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAOrI;AAED,wBAAgB,4BAA4B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAEzG;AAED,wBAAgB,+BAA+B,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAI5G;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAGlI;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,GAAG,MAAM,GAAG,SAAS,CAOrI"}
|
package/dist/commerce.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRESTACPCurrencyIfPossible = getRESTACPCurrencyIfPossible;
|
|
4
|
+
exports.getRESTACPTotalAmountIfPossible = getRESTACPTotalAmountIfPossible;
|
|
5
|
+
exports.getMCPACPCurrencyIfPossible = getMCPACPCurrencyIfPossible;
|
|
6
|
+
exports.getMCPACPTotalAmountIfPossible = getMCPACPTotalAmountIfPossible;
|
|
7
|
+
exports.getRESTUCPCurrencyIfPossible = getRESTUCPCurrencyIfPossible;
|
|
8
|
+
exports.getRESTUCPTotalAmountIfPossible = getRESTUCPTotalAmountIfPossible;
|
|
9
|
+
exports.getMCPUCPCurrencyIfPossible = getMCPUCPCurrencyIfPossible;
|
|
10
|
+
exports.getMCPUCPTotalAmountIfPossible = getMCPUCPTotalAmountIfPossible;
|
|
11
|
+
const headers_1 = require("./headers");
|
|
12
|
+
function getRESTACPCurrencyIfPossible(restACPResponseBody) {
|
|
13
|
+
return restACPResponseBody?.currency;
|
|
14
|
+
}
|
|
15
|
+
function getRESTACPTotalAmountIfPossible(restACPResponseBody) {
|
|
16
|
+
return restACPResponseBody?.totals?.find((total) => {
|
|
17
|
+
return total.type == "total";
|
|
18
|
+
})?.amount;
|
|
19
|
+
}
|
|
20
|
+
function getMCPACPCurrencyIfPossible(request, mcpResponseBody) {
|
|
21
|
+
const isProbablyACP = !(0, headers_1.getIsUCPCall)(request.headers) && (0, headers_1.getIsProbablyACPCall)(request.headers);
|
|
22
|
+
return isProbablyACP ? mcpResponseBody.result?.currency : undefined;
|
|
23
|
+
}
|
|
24
|
+
function getMCPACPTotalAmountIfPossible(request, mcpResponseBody) {
|
|
25
|
+
const isProbablyACP = !(0, headers_1.getIsUCPCall)(request.headers) && (0, headers_1.getIsProbablyACPCall)(request.headers);
|
|
26
|
+
return isProbablyACP
|
|
27
|
+
? mcpResponseBody.result?.totals?.find((total) => {
|
|
28
|
+
return total.type == "total";
|
|
29
|
+
})?.amount
|
|
30
|
+
: undefined;
|
|
31
|
+
}
|
|
32
|
+
function getRESTUCPCurrencyIfPossible(restUCPResponseBody) {
|
|
33
|
+
return restUCPResponseBody?.currency;
|
|
34
|
+
}
|
|
35
|
+
function getRESTUCPTotalAmountIfPossible(restUCPResponseBody) {
|
|
36
|
+
return restUCPResponseBody?.totals?.find((total) => {
|
|
37
|
+
return total.type == "total";
|
|
38
|
+
})?.amount;
|
|
39
|
+
}
|
|
40
|
+
function getMCPUCPCurrencyIfPossible(request, mcpResponseBody) {
|
|
41
|
+
const isUCP = (0, headers_1.getIsUCPCall)(request.headers);
|
|
42
|
+
return isUCP ? mcpResponseBody.result?.structuredContent?.currency : undefined;
|
|
43
|
+
}
|
|
44
|
+
function getMCPUCPTotalAmountIfPossible(request, mcpResponseBody) {
|
|
45
|
+
const isUCP = (0, headers_1.getIsUCPCall)(request.headers);
|
|
46
|
+
return isUCP
|
|
47
|
+
? mcpResponseBody.result?.structuredContent?.totals?.find((total) => {
|
|
48
|
+
return total.type == "total";
|
|
49
|
+
})?.amount
|
|
50
|
+
: undefined;
|
|
51
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http";
|
|
2
|
+
export declare function getFilteredHeaders<Headers extends IncomingHttpHeaders | OutgoingHttpHeaders>(headers: Headers): Headers;
|
|
3
|
+
export declare function getIsMCPCall(headers: IncomingHttpHeaders): boolean;
|
|
4
|
+
export declare function getIsUCPCall(headers: IncomingHttpHeaders): boolean;
|
|
5
|
+
export declare function getIsProbablyACPCall(headers: IncomingHttpHeaders): boolean;
|
|
6
|
+
//# sourceMappingURL=headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AA2BpE,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUvH;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAIlE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAI1E"}
|