@knownagents/sdk 1.11.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.
@@ -0,0 +1,133 @@
1
+ import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http";
2
+ /**
3
+ * ACP response details from a REST API call.
4
+ */
5
+ export interface RESTACPResponseBody {
6
+ /** The response currency, usually an ISO 4217 code. */
7
+ currency?: string;
8
+ /** ACP totals returned by the REST endpoint. The "total" item is sent to Known Agents. */
9
+ totals?: {
10
+ type?: string;
11
+ amount?: number;
12
+ }[];
13
+ }
14
+ /**
15
+ * UCP response details from a REST API call.
16
+ */
17
+ export interface RESTUCPResponseBody {
18
+ /** The response currency, usually an ISO 4217 code. */
19
+ currency?: string;
20
+ /** UCP totals returned by the REST endpoint. The "total" item is sent to Known Agents. */
21
+ totals?: {
22
+ type?: string;
23
+ amount?: number;
24
+ }[];
25
+ }
26
+ /**
27
+ * Additional metadata to include when tracking a pageview or REST API call.
28
+ */
29
+ export interface TrackPageviewOrRESTCallOptions {
30
+ /** ACP response body from the REST endpoint. */
31
+ restACPResponseBody?: RESTACPResponseBody;
32
+ /** UCP response body from the REST endpoint. */
33
+ restUCPResponseBody?: RESTUCPResponseBody;
34
+ }
35
+ /**
36
+ * A normalized visit payload for the Known Agents API.
37
+ * Use this with `trackVisits()` when batching requests yourself.
38
+ */
39
+ export interface VisitRequest {
40
+ /** The URL path and query string of the request (e.g. "/about?foo=bar") */
41
+ request_path: string;
42
+ /** The HTTP method (e.g. "GET", "POST") */
43
+ request_method: string;
44
+ /** The HTTP request headers. Sensitive headers are removed before sending. */
45
+ request_headers: IncomingHttpHeaders;
46
+ /** The HTTP response status code (e.g. 200, 404) */
47
+ response_status_code?: number;
48
+ /** The HTTP response headers. Sensitive headers are removed before sending. */
49
+ response_headers?: OutgoingHttpHeaders;
50
+ /** The response duration in milliseconds */
51
+ response_duration_in_milliseconds?: number;
52
+ /** The MCP JSON-RPC request method */
53
+ mcp_request_method?: string;
54
+ /** The MCP request tool name */
55
+ mcp_request_tool_name?: string;
56
+ /** The MCP request client name */
57
+ mcp_request_client_info_name?: string;
58
+ /** The MCP request client version */
59
+ mcp_request_client_info_version?: string;
60
+ /** Whether the MCP response result is an error */
61
+ mcp_response_result_is_error?: boolean;
62
+ /** The MCP response error code */
63
+ mcp_response_error_code?: number;
64
+ /** The MCP response error message */
65
+ mcp_response_error_message?: string;
66
+ /** The ACP response currency */
67
+ acp_response_currency?: string;
68
+ /** The ACP response "total" amount */
69
+ acp_response_total_amount?: number;
70
+ /** The UCP response currency */
71
+ ucp_response_currency?: string;
72
+ /** The UCP response "total" amount */
73
+ ucp_response_total_amount?: number;
74
+ /** The timestamp when the visit occurred (ISO 8601 format) */
75
+ created?: string;
76
+ }
77
+ /**
78
+ * An agent identification request for the Known Agents API.
79
+ * Used for batch agent identification with `identifyAgents()`.
80
+ */
81
+ export interface IdentificationRequest {
82
+ /** An identifier that will be echoed back in the response to match requests with results */
83
+ id?: string;
84
+ /** The HTTP request headers. Sensitive headers are removed before sending. */
85
+ request_headers: IncomingHttpHeaders;
86
+ }
87
+ /**
88
+ * The result of an agent identification request.
89
+ */
90
+ export interface IdentificationResult {
91
+ /** The identifier from the request (if provided) */
92
+ id?: string;
93
+ /**
94
+ * The identification result:
95
+ * - "verified": The agent was identified and verified
96
+ * - "verification_failed": The agent was identified but failed verification (spoofed)
97
+ * - "unknown_agent": The request is from an unrecognized agent or human
98
+ * - "not_verifiable": The agent was identified but cannot be verified
99
+ */
100
+ result: "verified" | "verification_failed" | "unknown_agent" | "not_verifiable";
101
+ /** The unique ID of the identified agent */
102
+ agent_id?: string;
103
+ /** The name of the identified agent (e.g. "Googlebot") */
104
+ agent_token?: string;
105
+ /** The documentation URL for the identified agent */
106
+ agent_url?: string;
107
+ /** The type of the identified agent (e.g. "AI Agent", "Search Engine Crawler") */
108
+ agent_type_name?: string;
109
+ /** The company behind the identified agent (e.g. "Google") */
110
+ operator_name?: string;
111
+ }
112
+ /**
113
+ * Agent types that can be controlled with robots.txt rules.
114
+ */
115
+ export declare enum AgentType {
116
+ AIAgent = "AI Agent",
117
+ AIAssistant = "AI Assistant",
118
+ AICodingAgent = "AI Coding Agent",
119
+ AIDataProvider = "AI Data Provider",
120
+ AIDataScraper = "AI Data Scraper",
121
+ AISearchCrawler = "AI Search Crawler",
122
+ Archiver = "Archiver",
123
+ AutomatedAgent = "Automated Agent",
124
+ DeveloperHelper = "Developer Helper",
125
+ Fetcher = "Fetcher",
126
+ IntelligenceGatherer = "Intelligence Gatherer",
127
+ Scraper = "Scraper",
128
+ SEOCrawler = "SEO Crawler",
129
+ SearchEngineCrawler = "Search Engine Crawler",
130
+ SecurityScanner = "Security Scanner",
131
+ UndocumentedAIAgent = "Undocumented AI Agent"
132
+ }
133
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentType = void 0;
4
+ /**
5
+ * Agent types that can be controlled with robots.txt rules.
6
+ */
7
+ var AgentType;
8
+ (function (AgentType) {
9
+ AgentType["AIAgent"] = "AI Agent";
10
+ AgentType["AIAssistant"] = "AI Assistant";
11
+ AgentType["AICodingAgent"] = "AI Coding Agent";
12
+ AgentType["AIDataProvider"] = "AI Data Provider";
13
+ AgentType["AIDataScraper"] = "AI Data Scraper";
14
+ AgentType["AISearchCrawler"] = "AI Search Crawler";
15
+ AgentType["Archiver"] = "Archiver";
16
+ AgentType["AutomatedAgent"] = "Automated Agent";
17
+ AgentType["DeveloperHelper"] = "Developer Helper";
18
+ AgentType["Fetcher"] = "Fetcher";
19
+ AgentType["IntelligenceGatherer"] = "Intelligence Gatherer";
20
+ AgentType["Scraper"] = "Scraper";
21
+ AgentType["SEOCrawler"] = "SEO Crawler";
22
+ AgentType["SearchEngineCrawler"] = "Search Engine Crawler";
23
+ AgentType["SecurityScanner"] = "Security Scanner";
24
+ AgentType["UndocumentedAIAgent"] = "Undocumented AI Agent";
25
+ })(AgentType || (exports.AgentType = AgentType = {}));
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "@knownagents/sdk",
3
- "version": "1.11.0",
3
+ "version": "2.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 verification",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
+ "files": [
9
+ "dist",
10
+ "src"
11
+ ],
8
12
  "keywords": [
9
13
  "ai analytics",
10
14
  "bot traffic",
@@ -16,6 +20,12 @@
16
20
  "bot detection",
17
21
  "crawler detection",
18
22
  "llm analytics",
23
+ "mcp analytics",
24
+ "mcp server analytics",
25
+ "model context protocol",
26
+ "agentic commerce protocol",
27
+ "universal commerce protocol",
28
+ "commerce analytics",
19
29
  "robots.txt",
20
30
  "automatic robots.txt",
21
31
  "robots.txt generator",
@@ -54,5 +64,8 @@
54
64
  "devDependencies": {
55
65
  "@types/node": "^22.19.17",
56
66
  "typescript": "^5.8.2"
67
+ },
68
+ "dependencies": {
69
+ "@modelcontextprotocol/sdk": "^1.29.0"
57
70
  }
58
71
  }
@@ -0,0 +1,324 @@
1
+ import type { IncomingMessage, ServerResponse } from "http"
2
+ import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
3
+ import {
4
+ getMCPACPCurrencyIfPossible,
5
+ getMCPACPTotalAmountIfPossible,
6
+ getMCPUCPCurrencyIfPossible,
7
+ getMCPUCPTotalAmountIfPossible,
8
+ getRESTACPCurrencyIfPossible,
9
+ getRESTACPTotalAmountIfPossible,
10
+ getRESTUCPCurrencyIfPossible,
11
+ getRESTUCPTotalAmountIfPossible
12
+ } from "./commerce"
13
+ import { getFilteredHeaders, getIsMCPCall } from "./headers"
14
+ import { getIsPathExcluded } from "./paths"
15
+ import type {
16
+ AgentType,
17
+ IdentificationRequest,
18
+ IdentificationResult,
19
+ TrackPageviewOrRESTCallOptions,
20
+ VisitRequest
21
+ } from "./types"
22
+
23
+ interface MCPRequestBody {
24
+ jsonrpc: string
25
+ method: string
26
+ params?: {
27
+ name?: string
28
+ clientInfo?: {
29
+ name?: string
30
+ version?: string
31
+ }
32
+ }
33
+ }
34
+
35
+ interface MCPResponseBody {
36
+ jsonrpc: string
37
+ result?: {
38
+ isError?: boolean
39
+ currency?: string
40
+ totals?: {
41
+ type?: string
42
+ amount?: number
43
+ }[]
44
+ structuredContent?: {
45
+ currency?: string
46
+ totals?: {
47
+ type?: string
48
+ amount?: number
49
+ }[]
50
+ }
51
+ }
52
+ error?: {
53
+ code?: number
54
+ message?: string
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Server-side client for Known Agents analytics, robots.txt generation, and agent identification.
60
+ */
61
+ export class KnownAgents {
62
+ private readonly accessToken: string
63
+
64
+ /**
65
+ * Creates a new instance of the Known Agents client.
66
+ *
67
+ * @param accessToken - Your project's access token.
68
+ */
69
+ constructor(accessToken: string) {
70
+ this.accessToken = accessToken
71
+ }
72
+
73
+ /**
74
+ * Tracks a single non-MCP pageview or REST API call for analytics.
75
+ * The visit is sent after the response finishes so status, headers, and duration are included.
76
+ * This method is non-blocking and handles errors internally.
77
+ *
78
+ * @param request - The incoming Node.js HTTP request.
79
+ * @param response - The outgoing Node.js HTTP response.
80
+ * @param options - Optional ACP or UCP response metadata for REST API calls.
81
+ */
82
+ trackPageviewOrRESTCall(request: IncomingMessage, response: ServerResponse, options?: TrackPageviewOrRESTCallOptions): void {
83
+ const isMCPCall = getIsMCPCall(request.headers)
84
+ const path = request.url
85
+ const method = request.method
86
+
87
+ if (isMCPCall || !path || getIsPathExcluded(path) || !method) {
88
+ return
89
+ }
90
+
91
+ const created = new Date()
92
+
93
+ const sendVisit = () => {
94
+ const responseDurationInMilliseconds = Date.now() - created.getTime()
95
+
96
+ const visitRequest: VisitRequest = {
97
+ request_path: path,
98
+ request_method: method,
99
+ request_headers: request.headers,
100
+ response_status_code: response?.statusCode,
101
+ response_headers: response.getHeaders(),
102
+ response_duration_in_milliseconds: responseDurationInMilliseconds,
103
+ acp_response_currency: options?.restACPResponseBody ? getRESTACPCurrencyIfPossible(options?.restACPResponseBody) : undefined,
104
+ acp_response_total_amount: options?.restACPResponseBody ? getRESTACPTotalAmountIfPossible(options?.restACPResponseBody) : undefined,
105
+ ucp_response_currency: options?.restUCPResponseBody ? getRESTUCPCurrencyIfPossible(options?.restUCPResponseBody) : undefined,
106
+ ucp_response_total_amount: options?.restUCPResponseBody ? getRESTUCPTotalAmountIfPossible(options?.restUCPResponseBody) : undefined,
107
+ created: created.toISOString()
108
+ }
109
+
110
+ this.trackVisits([visitRequest])
111
+ }
112
+
113
+ if (response.writableEnded) {
114
+ sendVisit()
115
+ } else {
116
+ response.once("finish", sendVisit)
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Tracks a single MCP call handled by `StreamableHTTPServerTransport`.
122
+ * Call this after connecting the transport and before the transport processes the request so request and response metadata can be captured.
123
+ * Do not use this with transport instances shared across concurrent requests; use `trackVisits()` instead.
124
+ * This method is non-blocking and handles errors internally.
125
+ *
126
+ * @param request - The incoming Node.js HTTP request.
127
+ * @param response - The outgoing Node.js HTTP response.
128
+ * @param transport - The MCP streamable HTTP transport handling the request.
129
+ */
130
+ trackMCPCall(request: IncomingMessage, response: ServerResponse, transport: StreamableHTTPServerTransport): void {
131
+ const isMCPCall = getIsMCPCall(request.headers)
132
+ const path = request.url
133
+ const method = request.method
134
+
135
+ if (!isMCPCall || !path || getIsPathExcluded(path) || !method) {
136
+ return
137
+ }
138
+
139
+ const created = new Date()
140
+
141
+ let mcpRequestBody: MCPRequestBody | undefined
142
+ let mcpResponseBody: MCPResponseBody | undefined
143
+
144
+ const originalTransportOnMessage = transport.onmessage
145
+ transport.onmessage = (message, extra): void => {
146
+ mcpRequestBody = message as MCPRequestBody
147
+ originalTransportOnMessage?.(message, extra)
148
+ }
149
+
150
+ const originalTransportSend = transport.send
151
+ transport.send = async (message, options): Promise<void> => {
152
+ mcpResponseBody = message as MCPResponseBody
153
+ await originalTransportSend.call(transport, message, options)
154
+ }
155
+
156
+ const restoreTransport = () => {
157
+ transport.onmessage = originalTransportOnMessage
158
+ transport.send = originalTransportSend
159
+ }
160
+
161
+ const sendVisit = () => {
162
+ restoreTransport()
163
+
164
+ if (!mcpRequestBody || !mcpResponseBody) {
165
+ return
166
+ }
167
+
168
+ const responseDurationInMilliseconds = Date.now() - created.getTime()
169
+
170
+ const visitRequest: VisitRequest = {
171
+ request_path: path,
172
+ request_method: method,
173
+ request_headers: request.headers,
174
+ response_status_code: response?.statusCode,
175
+ response_headers: response.getHeaders(),
176
+ response_duration_in_milliseconds: responseDurationInMilliseconds,
177
+ mcp_request_method: mcpRequestBody.method,
178
+ mcp_request_tool_name: mcpRequestBody.params?.name,
179
+ mcp_request_client_info_name: mcpRequestBody.params?.clientInfo?.name,
180
+ mcp_request_client_info_version: mcpRequestBody.params?.clientInfo?.version,
181
+ mcp_response_result_is_error: mcpResponseBody.result?.isError,
182
+ mcp_response_error_code: mcpResponseBody.error?.code,
183
+ mcp_response_error_message: mcpResponseBody.error?.message,
184
+ acp_response_currency: getMCPACPCurrencyIfPossible(request, mcpResponseBody),
185
+ acp_response_total_amount: getMCPACPTotalAmountIfPossible(request, mcpResponseBody),
186
+ ucp_response_currency: getMCPUCPCurrencyIfPossible(request, mcpResponseBody),
187
+ ucp_response_total_amount: getMCPUCPTotalAmountIfPossible(request, mcpResponseBody),
188
+ created: created.toISOString()
189
+ }
190
+
191
+ this.trackVisits([visitRequest])
192
+ }
193
+
194
+ if (response.writableEnded) {
195
+ sendVisit()
196
+ } else {
197
+ response.once("finish", sendVisit)
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Tracks multiple visits in a batch for analytics.
203
+ * This method is non-blocking and handles errors internally.
204
+ * Recommended for high-traffic websites - batch visits and send periodically (e.g. every 30 seconds).
205
+ *
206
+ * @param requests - An array of visits.
207
+ */
208
+ trackVisits(requests: VisitRequest[]): void {
209
+ const filteredRequests = requests
210
+ .filter((request) => {
211
+ return !getIsPathExcluded(request.request_path)
212
+ })
213
+ .map((request) => {
214
+ return {
215
+ ...request,
216
+ request_headers: getFilteredHeaders(request.request_headers),
217
+ response_headers: request.response_headers ? getFilteredHeaders(request.response_headers) : undefined,
218
+ }
219
+ })
220
+
221
+ if (filteredRequests.length === 0) {
222
+ return
223
+ }
224
+
225
+ fetch("https://api.knownagents.com/visits", {
226
+ method: "POST",
227
+ headers: {
228
+ "Authorization": `Bearer ${this.accessToken}`,
229
+ "Content-Type": "application/json"
230
+ },
231
+ body: JSON.stringify(filteredRequests)
232
+ }).catch(error => {
233
+ console.error(`Known Agents failed to track visits: ${error.message}`)
234
+ })
235
+ }
236
+
237
+ /**
238
+ * Generates a robots.txt file that disallows the specified agent types.
239
+ * The generated file stays up-to-date with all current and future agents in the specified categories.
240
+ * Cache and serve the result at your website's `/robots.txt` endpoint.
241
+ *
242
+ * @param agentTypes - An array of agent types to disallow.
243
+ * @param disallow - The path to disallow (default: "/" for all paths).
244
+ * @returns A promise that resolves to the generated robots.txt content as a string.
245
+ * @throws {Error} If the API call fails or returns a non-200 status.
246
+ */
247
+ async generateRobotsTXT(agentTypes: AgentType[], disallow: string = "/"): Promise<string> {
248
+ const response = await fetch("https://api.knownagents.com/robots-txts", {
249
+ method: "POST",
250
+ headers: {
251
+ "Authorization": `Bearer ${this.accessToken}`,
252
+ "Content-Type": "application/json"
253
+ },
254
+ body: JSON.stringify({
255
+ agent_types: agentTypes,
256
+ disallow: disallow
257
+ })
258
+ })
259
+
260
+ if (response.ok) {
261
+ return await response.text()
262
+ } else {
263
+ throw new Error(`Known Agents failed to generate robots.txt: ${response.status} ${response.statusText}`)
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Identifies and verifies a single agent from an HTTP request.
269
+ * Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
270
+ *
271
+ * @param request - The incoming Node.js HTTP request.
272
+ * @returns A promise that resolves to the identification result.
273
+ * @throws {Error} If the API call fails or returns a non-200 status.
274
+ */
275
+ async identifyAgent(request: IncomingMessage): Promise<IdentificationResult> {
276
+ const response = await fetch("https://api.knownagents.com/agent-identifications", {
277
+ method: "POST",
278
+ headers: {
279
+ "Authorization": `Bearer ${this.accessToken}`,
280
+ "Content-Type": "application/json"
281
+ },
282
+ body: JSON.stringify({
283
+ request_headers: getFilteredHeaders(request.headers)
284
+ })
285
+ })
286
+
287
+ if (response.ok) {
288
+ return await response.json()
289
+ } else {
290
+ throw new Error(`Known Agents failed to identify agent: ${response.status} ${response.statusText}`)
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Identifies and verifies multiple agents from HTTP requests in a batch.
296
+ * Uses Web Bot Auth (HTTP message signatures), IP matching, or other available methods.
297
+ *
298
+ * @param requests - An array of identification requests.
299
+ * @returns A promise that resolves to an array of identification results.
300
+ * @throws {Error} If the API call fails or returns a non-200 status.
301
+ */
302
+ async identifyAgents(requests: IdentificationRequest[]): Promise<IdentificationResult[]> {
303
+ const response = await fetch("https://api.knownagents.com/agent-identifications", {
304
+ method: "POST",
305
+ headers: {
306
+ "Authorization": `Bearer ${this.accessToken}`,
307
+ "Content-Type": "application/json"
308
+ },
309
+ body: JSON.stringify(requests.map(request => {
310
+ return {
311
+ ...request,
312
+ request_headers: getFilteredHeaders(request.request_headers),
313
+ }
314
+ }))
315
+ })
316
+
317
+ if (response.ok) {
318
+ return await response.json()
319
+ } else {
320
+ throw new Error(`Known Agents failed to identify agents: ${response.status} ${response.statusText}`)
321
+ }
322
+ }
323
+
324
+ }
@@ -0,0 +1,68 @@
1
+ import type { IncomingMessage } from "http"
2
+ import { getIsProbablyACPCall, getIsUCPCall } from "./headers"
3
+ import type { RESTACPResponseBody, RESTUCPResponseBody } from "./types"
4
+
5
+ interface MCPCommerceResponseBody {
6
+ result?: {
7
+ currency?: string
8
+ totals?: {
9
+ type?: string
10
+ amount?: number
11
+ }[]
12
+ structuredContent?: {
13
+ currency?: string
14
+ totals?: {
15
+ type?: string
16
+ amount?: number
17
+ }[]
18
+ }
19
+ }
20
+ }
21
+
22
+ export function getRESTACPCurrencyIfPossible(restACPResponseBody: RESTACPResponseBody): string | undefined {
23
+ return restACPResponseBody?.currency
24
+ }
25
+
26
+ export function getRESTACPTotalAmountIfPossible(restACPResponseBody: RESTACPResponseBody): number | undefined {
27
+ return restACPResponseBody?.totals?.find((total) => {
28
+ return total.type == "total"
29
+ })?.amount
30
+ }
31
+
32
+ export function getMCPACPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined {
33
+ const isProbablyACP = !getIsUCPCall(request.headers) && getIsProbablyACPCall(request.headers)
34
+ return isProbablyACP ? mcpResponseBody.result?.currency : undefined
35
+ }
36
+
37
+ export function getMCPACPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined {
38
+ const isProbablyACP = !getIsUCPCall(request.headers) && getIsProbablyACPCall(request.headers)
39
+ return isProbablyACP
40
+ ? mcpResponseBody.result?.totals?.find((total) => {
41
+ return total.type == "total"
42
+ })?.amount
43
+ : undefined
44
+ }
45
+
46
+ export function getRESTUCPCurrencyIfPossible(restUCPResponseBody: RESTUCPResponseBody): string | undefined {
47
+ return restUCPResponseBody?.currency
48
+ }
49
+
50
+ export function getRESTUCPTotalAmountIfPossible(restUCPResponseBody: RESTUCPResponseBody): number | undefined {
51
+ return restUCPResponseBody?.totals?.find((total) => {
52
+ return total.type == "total"
53
+ })?.amount
54
+ }
55
+
56
+ export function getMCPUCPCurrencyIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): string | undefined {
57
+ const isUCP = getIsUCPCall(request.headers)
58
+ return isUCP ? mcpResponseBody.result?.structuredContent?.currency : undefined
59
+ }
60
+
61
+ export function getMCPUCPTotalAmountIfPossible(request: IncomingMessage, mcpResponseBody: MCPCommerceResponseBody): number | undefined {
62
+ const isUCP = getIsUCPCall(request.headers)
63
+ return isUCP
64
+ ? mcpResponseBody.result?.structuredContent?.totals?.find((total) => {
65
+ return total.type == "total"
66
+ })?.amount
67
+ : undefined
68
+ }
package/src/headers.ts ADDED
@@ -0,0 +1,56 @@
1
+ import type { IncomingHttpHeaders, OutgoingHttpHeaders } from "http"
2
+
3
+ const filteredHeaderNames = [
4
+ "authorization",
5
+ "proxy-authorization",
6
+ "cookie",
7
+ "set-cookie",
8
+ "x-api-key",
9
+ "api-key",
10
+ "apikey",
11
+ "x-auth-token",
12
+ "x-access-token",
13
+ "x-id-token",
14
+ "x-refresh-token",
15
+ "x-session-token",
16
+ "session-token",
17
+ "csrf-token",
18
+ "x-csrf-token",
19
+ "xsrf-token",
20
+ "x-xsrf-token",
21
+ "x-amz-security-token",
22
+ "x-goog-api-key",
23
+ "x-client-secret",
24
+ "client-secret",
25
+ "private-token"
26
+ ]
27
+
28
+ export function getFilteredHeaders<Headers extends IncomingHttpHeaders | OutgoingHttpHeaders>(headers: Headers): Headers {
29
+ const filteredHeaders = { ...headers }
30
+
31
+ Object.keys(filteredHeaders).forEach((key) => {
32
+ if (filteredHeaderNames.includes(key.toLowerCase())) {
33
+ delete filteredHeaders[key]
34
+ }
35
+ })
36
+
37
+ return filteredHeaders
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 getIsProbablyACPCall(headers: IncomingHttpHeaders): boolean {
53
+ return Object.keys(headers).some((key) => {
54
+ return key.toLowerCase() === "api-version"
55
+ })
56
+ }