@agentcash/discovery 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  Canonical discovery runtime for the agentcash ecosystem.
4
4
 
5
- Use one library for CLI, server, and client so discovery behavior is identical everywhere.
5
+ Use one library for MCP, CLI, router, and audit so discovery behavior is identical everywhere.
6
6
 
7
7
  ## Why One Library
8
8
 
9
9
  - Same parsing logic across surfaces: no CLI/server/client drift.
10
+ - Shared Zod schema that the router can test against at compile-time.
10
11
  - Same warning codes and precedence rules: fewer integration surprises.
11
12
  - Same compatibility adapters in one place: legacy behavior is isolated and removable.
12
- - Same L0-L5 harness model: easier context-budget auditing and rollout decisions.
13
13
 
14
14
  ## L0-L5 Mental Model
15
15
 
@@ -22,196 +22,128 @@ Use one library for CLI, server, and client so discovery behavior is identical e
22
22
 
23
23
  Design rule: `L0` + `L1` are zero-hop critical. `L2+` should be fetched on demand.
24
24
 
25
+ In practice, each layer should guide the agent to discover the next:
26
+
27
+ | Layer | Surface | What the agent gets |
28
+ | ------ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
29
+ | **L0** | MCP tool description / `agentcash --help` | First impression of agentcash. Should encourage the agent to use it and explicitly explain `discoverOriginSchema` and `checkEndpointSchema`. |
30
+ | **L1** | Same location as L0 | List of domains available to the agent. Each entry should be descriptive enough for the agent to understand what it does at a high level. |
31
+ | **L2** | `discoverOriginSchema` result | Detailed description of the origin and its supported endpoints. |
32
+ | **L3** | `checkEndpointSchema` result | Specific guidance for a single endpoint: input/output schema, auth mode, and a detailed description of what the endpoint does. |
33
+ | **L4** | `discoverOriginSchema` with `includeGuidance: true` | Composition guidance for 2+ resources at an origin. Sourced from the `guidance` field in OpenAPI. |
34
+
25
35
  ## Install
26
36
 
27
37
  ```bash
28
38
  pnpm add @agentcash/discovery
29
39
  ```
30
40
 
31
- ## CLI Usage
41
+ ## CLI
32
42
 
33
- Quick audit:
43
+ Two commands: `discover` (list endpoints at an origin) and `check` (inspect a specific URL).
34
44
 
35
45
  ```bash
46
+ # Discover all endpoints at an origin
36
47
  npx @agentcash/discovery stabletravel.dev
37
- ```
48
+ npx @agentcash/discovery discover stabletravel.dev
38
49
 
39
- Verbose matrices:
40
-
41
- ```bash
42
- npx @agentcash/discovery stabletravel.dev -v
50
+ # Inspect a specific endpoint URL
51
+ npx @agentcash/discovery check https://stabletravel.dev/search
43
52
  ```
44
53
 
45
- Machine-readable output:
54
+ Flags:
46
55
 
47
- ```bash
48
- npx @agentcash/discovery stabletravel.dev --json
49
- ```
56
+ | Flag | Description |
57
+ | -------- | -------------------------------------------------- |
58
+ | `--json` | Machine-readable JSON output |
59
+ | `-v` | Verbose — includes guidance text and warning hints |
50
60
 
51
- L0-L5 context harness summary for a client:
61
+ JSON output shape (`discover`):
52
62
 
53
- ```bash
54
- npx @agentcash/discovery stabletravel.dev --harness --client claude-code
63
+ ```json
64
+ {
65
+ "ok": true,
66
+ "selectedStage": "openapi",
67
+ "resources": [{ "resourceKey": "GET /search", "method": "GET", "path": "/search" }],
68
+ "warnings": [],
69
+ "meta": { "origin": "https://stabletravel.dev", "specUrl": "..." }
70
+ }
55
71
  ```
56
72
 
57
- L0-L5 harness verbose with explicit budget:
58
-
59
- ```bash
60
- npx @agentcash/discovery stabletravel.dev --harness -v --client skill-cli --context-window-tokens 200000
73
+ JSON output shape (`check`):
74
+
75
+ ```json
76
+ {
77
+ "url": "https://stabletravel.dev/search",
78
+ "found": true,
79
+ "origin": "https://stabletravel.dev",
80
+ "path": "/search",
81
+ "advisories": [{ "method": "GET", "authMode": "bearer", "estimatedPrice": "$0.01" }],
82
+ "warnings": []
83
+ }
61
84
  ```
62
85
 
63
- Useful flags:
64
-
65
- - `--compat on|off|strict`
66
- - `--probe`
67
- - `--timeout-ms <ms>`
68
- - `--no-truncate`
69
- - `--no-color`
70
-
71
86
  ## Programmatic Usage
72
87
 
73
88
  ```ts
74
- import {
75
- auditContextHarness,
76
- discover,
77
- discoverDetailed,
78
- validatePaymentRequiredDetailed,
79
- type HarnessClientId,
80
- } from '@agentcash/discovery';
81
-
82
- const progressive = await discover({
83
- target: 'stabletravel.dev',
84
- compatMode: 'on',
85
- });
86
-
87
- const detailed = await discoverDetailed({
88
- target: 'stabletravel.dev',
89
- compatMode: 'strict',
90
- rawView: 'full',
91
- });
92
-
93
- const client: HarnessClientId = 'claude-code';
94
- const harness = await auditContextHarness({
95
- target: 'stabletravel.dev',
96
- client,
97
- contextWindowTokens: 200000,
98
- compatMode: 'on',
99
- });
100
-
101
- const validation = validatePaymentRequiredDetailed(payload, {
102
- compatMode: 'strict',
103
- metadata: {
104
- title: 'Example API',
105
- description: 'Sample description',
106
- favicon: 'https://example.com/favicon.ico',
107
- ogImages: [{ url: 'https://example.com/og.png' }],
108
- },
109
- });
110
- ```
111
-
112
- ## MCP Adapter Contract
113
-
114
- This section is the canonical contract for MCP-facing discovery adapters:
115
-
116
- - `discoverForMcp(options)` for L2 resources + L4 guidance policy projection.
117
- - `inspectEndpointForMcp(options)` for L3 OpenAPI advisory projection.
118
-
119
- `discoverForMcp` guarantees:
120
-
121
- - `guidanceAvailable` is always present.
122
- - `guidanceTokens` is present when guidance exists.
123
- - `guidance` is included when `includeGuidance=true`, excluded when
124
- `includeGuidance=false`, and auto-included under the token threshold when not
125
- specified.
126
-
127
- `inspectEndpointForMcp` guarantees:
128
-
129
- - Spec-derived HTTP methods for the selected endpoint path.
130
- - Per-method advisory data: summary, estimated price, protocols, auth mode, and
131
- input schema.
132
-
133
- Ownership boundary:
134
-
135
- - `@agentcash/discovery` owns discovery/advisory contracts.
136
- - Runtime probe truth (live 402 parsing/payment option extraction/divergence)
137
- belongs to the `agentcash` MCP package.
138
-
139
- Reference integration boundary:
140
-
141
- - `https://github.com/Merit-Systems/agentcash/blob/main/packages/external/mcp/docs/discovery-boundary.md`
89
+ import { discoverOriginSchema, checkEndpointSchema } from '@agentcash/discovery';
142
90
 
143
- ## Discovery Waterfall
91
+ // Discover all endpoints at an origin
92
+ const result = await discoverOriginSchema({ target: 'stabletravel.dev' });
93
+ // result.found === true → result.endpoints (L2Route[]), result.guidance?, result.guidanceTokens?
144
94
 
145
- Default order:
146
-
147
- 1. Explicit override URLs (if provided)
148
- 2. OpenAPI (`/openapi.json`, then `/.well-known/openapi.json`)
149
- 3. `/.well-known/x402` (compat)
150
- 4. DNS TXT `_x402` pointers (compat)
151
- 5. Probe fallback (only when probe candidates are provided)
95
+ // Inspect a specific endpoint URL
96
+ const check = await checkEndpointSchema({ url: 'https://stabletravel.dev/search' });
97
+ // check.found === true check.advisories (per-method: authMode, estimatedPrice, protocols, inputSchema)
98
+ ```
152
99
 
153
- Behavior:
100
+ ## Exported API
154
101
 
155
- - `discover(...)` stops at first valid non-empty stage.
156
- - `discoverDetailed(...)` runs full waterfall and merges deterministically.
102
+ ### Core discovery
157
103
 
158
- Validation behavior:
104
+ | Export | Description |
105
+ | ------------------------ | --------------------------------------------------------- |
106
+ | `discoverOriginSchema()` | Progressive discovery — returns endpoints + advisory data |
107
+ | `checkEndpointSchema()` | Per-endpoint inspection — returns per-method advisories |
159
108
 
160
- - `validatePaymentRequiredDetailed(...)` uses Coinbase `@x402/core` schemas as the structural base gate.
161
- - Product policy diagnostics (network/schema/metadata) are layered on top with stable issue codes.
109
+ ### Layer fetchers (low-level)
162
110
 
163
- ## Compatibility Modes
111
+ | Export | Layer | Description |
112
+ | -------------------------- | ----- | ------------------------------------- |
113
+ | `getOpenAPI(origin)` | — | Fetch OpenAPI spec from origin |
114
+ | `getWellKnown(origin)` | — | Fetch `/.well-known/x402` document |
115
+ | `getProbe(url, body?)` | — | Live endpoint probe |
116
+ | `checkL2ForOpenAPI(spec)` | L2 | Extract route list from OpenAPI |
117
+ | `checkL2ForWellknown(doc)` | L2 | Extract route list from well-known |
118
+ | `getL3(origin, path)` | L3 | Get detailed metadata for an endpoint |
119
+ | `checkL4ForOpenAPI(spec)` | L4 | Extract guidance from OpenAPI |
120
+ | `checkL4ForWellknown(doc)` | L4 | Extract guidance from well-known |
164
121
 
165
- - `on` (default): legacy adapters enabled.
166
- - `off`: canonical-only behavior.
167
- - `strict`: legacy adapters enabled, selected warnings escalated.
122
+ ### Validation
168
123
 
169
- ## Contract Guarantees
124
+ | Export | Description |
125
+ | ----------------------------------- | -------------------------------------------- |
126
+ | `validatePaymentRequiredDetailed()` | Full 402 payload validation with diagnostics |
127
+ | `evaluateMetadataCompleteness()` | Metadata quality score |
128
+ | `VALIDATION_CODES` | Stable issue code constants |
170
129
 
171
- Resource identity:
130
+ ### Audit
172
131
 
173
- ```text
174
- ${origin} ${method} ${path}
175
- ```
132
+ | Export | Description |
133
+ | --------------------------- | ------------------------------ |
134
+ | `getWarningsForOpenAPI()` | Warnings for OpenAPI source |
135
+ | `getWarningsForWellKnown()` | Warnings for well-known source |
136
+ | `getWarningsForL2()` | Warnings for route list |
137
+ | `getWarningsForL3()` | Warnings for endpoint metadata |
138
+ | `getWarningsForL4()` | Warnings for guidance layer |
139
+ | `AUDIT_CODES` | Stable audit code constants |
176
140
 
177
- Required normalized fields:
141
+ Ownership boundary:
178
142
 
179
- - `resourceKey`
180
- - `origin`
181
- - `method`
182
- - `path`
183
- - `source`
184
- - `verified` (default `false`)
143
+ - `@agentcash/discovery` owns discovery/advisory contracts.
144
+ - `@agentcash` should own all signing logic, but should be composable with the methods for probing built in this package.
185
145
 
186
146
  Philosophy boundary:
187
147
 
188
148
  - Machine-parsable discovery metadata belongs in OpenAPI.
189
- - `llms.txt` is optional, unstructured guidance.
190
149
  - Discovery is advisory. Runtime payment challenge/probe is authoritative.
191
-
192
- ## Internal Registry Audit Harness
193
-
194
- For x402scan registry benchmarking:
195
-
196
- ```bash
197
- SCAN_DATABASE_URL='postgresql://...' pnpm audit:registry
198
- ```
199
-
200
- Quick sample:
201
-
202
- ```bash
203
- SCAN_DATABASE_URL='postgresql://...' pnpm audit:registry:quick
204
- ```
205
-
206
- Output:
207
-
208
- - `audit/registry-audit-<timestamp>.json`
209
- - `audit/registry-audit-latest.json`
210
-
211
- ## Deeper Docs
212
-
213
- Architecture and planning artifacts live in `.claude/`.
214
-
215
- Validation design doc:
216
-
217
- - `docs/VALIDATION_DIAGNOSTICS_DESIGN_2026-03-03.md`
package/dist/cli.cjs CHANGED
@@ -76,6 +76,7 @@ var OpenApiDocSchema = import_zod.z.object({
76
76
  description: import_zod.z.string().optional(),
77
77
  guidance: import_zod.z.string().optional()
78
78
  }),
79
+ security: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))).optional(),
79
80
  servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string() })).optional(),
80
81
  tags: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string() })).optional(),
81
82
  components: import_zod.z.object({ securitySchemes: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional() }).optional(),
@@ -105,20 +106,31 @@ var WellKnownParsedSchema = import_zod.z.object({
105
106
  function isRecord(value) {
106
107
  return value !== null && typeof value === "object" && !Array.isArray(value);
107
108
  }
108
- function hasSecurity(operation, scheme) {
109
- return operation.security?.some((s) => scheme in s) ?? false;
110
- }
111
- function has402Response(operation) {
112
- return Boolean(operation.responses?.["402"]);
109
+ function resolveSecurityFlags(requirements, securitySchemes) {
110
+ let hasApiKey = false;
111
+ let hasSiwx = false;
112
+ for (const requirement of requirements) {
113
+ for (const schemeName of Object.keys(requirement)) {
114
+ if (schemeName === "siwx") {
115
+ hasSiwx = true;
116
+ continue;
117
+ }
118
+ if (schemeName === "apiKey") {
119
+ hasApiKey = true;
120
+ continue;
121
+ }
122
+ const def = securitySchemes[schemeName];
123
+ if (isRecord(def) && def["type"] === "apiKey") hasApiKey = true;
124
+ }
125
+ }
126
+ return { hasApiKey, hasSiwx };
113
127
  }
114
- function inferAuthMode(operation) {
128
+ function inferAuthMode(operation, globalSecurity, securitySchemes) {
115
129
  const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
116
- const hasPayment = hasXPaymentInfo || has402Response(operation);
117
- const hasApiKey = hasSecurity(operation, "apiKey");
118
- const hasSiwx = hasSecurity(operation, "siwx");
119
- if (hasPayment && hasApiKey) return "apiKey+paid";
130
+ const effectiveSecurity = operation.security !== void 0 && operation.security.length > 0 ? operation.security : globalSecurity ?? [];
131
+ const { hasApiKey, hasSiwx } = resolveSecurityFlags(effectiveSecurity, securitySchemes ?? {});
132
+ if (hasXPaymentInfo && hasApiKey) return "apiKey+paid";
120
133
  if (hasXPaymentInfo) return "paid";
121
- if (hasPayment) return hasSiwx ? "siwx" : "paid";
122
134
  if (hasApiKey) return "apiKey";
123
135
  if (hasSiwx) return "siwx";
124
136
  return void 0;
@@ -138,10 +150,12 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
138
150
  var DEFAULT_MISSING_METHOD = "POST";
139
151
 
140
152
  // src/core/lib/url.ts
141
- function normalizeOrigin(target) {
153
+ function ensureProtocol(target) {
142
154
  const trimmed = target.trim();
143
- const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
144
- const url = new URL(withProtocol);
155
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
156
+ }
157
+ function normalizeOrigin(target) {
158
+ const url = new URL(ensureProtocol(target));
145
159
  url.pathname = "";
146
160
  url.search = "";
147
161
  url.hash = "";
@@ -180,7 +194,7 @@ function fetchSafe(url, init) {
180
194
  }
181
195
 
182
196
  // src/mmm-enabled.ts
183
- var isMmmEnabled = () => "1.0.1".includes("-mmm");
197
+ var isMmmEnabled = () => "1.0.2".includes("-mmm");
184
198
 
185
199
  // src/core/source/openapi/index.ts
186
200
  var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
@@ -189,15 +203,13 @@ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
189
203
  for (const httpMethod of [...HTTP_METHODS]) {
190
204
  const operation = pathItem[httpMethod.toLowerCase()];
191
205
  if (!operation) continue;
192
- const authMode = inferAuthMode(operation) ?? void 0;
206
+ const authMode = inferAuthMode(operation, doc.security, doc.components?.securitySchemes) ?? void 0;
193
207
  if (!authMode) continue;
194
208
  const p = operation["x-payment-info"];
195
209
  const protocols = (p?.protocols ?? []).filter(
196
210
  (proto) => proto !== "mpp" || isMmmEnabled()
197
211
  );
198
- if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
199
- if (authMode === "paid" && protocols.length === 0) continue;
200
- const pricing = authMode === "paid" && p ? {
212
+ const pricing = (authMode === "paid" || authMode === "apiKey+paid") && p ? {
201
213
  pricingMode: p.pricingMode,
202
214
  ...p.price ? { price: p.price } : {},
203
215
  ...p.minPrice ? { minPrice: p.minPrice } : {},
@@ -985,12 +997,12 @@ function getAdvisoriesForProbe(probe, path) {
985
997
  });
986
998
  }
987
999
  async function checkEndpointSchema(options) {
988
- const endpoint = new URL(options.url);
1000
+ const endpoint = new URL(ensureProtocol(options.url));
989
1001
  const origin = normalizeOrigin(endpoint.origin);
990
1002
  const path = normalizePath(endpoint.pathname || "/");
991
1003
  if (options.sampleInputBody !== void 0) {
992
1004
  const probeResult2 = await getProbe(
993
- options.url,
1005
+ endpoint.href,
994
1006
  options.headers,
995
1007
  options.signal,
996
1008
  options.sampleInputBody
@@ -1015,7 +1027,7 @@ async function checkEndpointSchema(options) {
1015
1027
  if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
1016
1028
  return { found: false, origin, path, cause: "not_found" };
1017
1029
  }
1018
- const probeResult = await getProbe(options.url, options.headers, options.signal);
1030
+ const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
1019
1031
  if (probeResult.isErr()) {
1020
1032
  return {
1021
1033
  found: false,
package/dist/cli.js CHANGED
@@ -50,6 +50,7 @@ var OpenApiDocSchema = z.object({
50
50
  description: z.string().optional(),
51
51
  guidance: z.string().optional()
52
52
  }),
53
+ security: z.array(z.record(z.string(), z.array(z.string()))).optional(),
53
54
  servers: z.array(z.object({ url: z.string() })).optional(),
54
55
  tags: z.array(z.object({ name: z.string() })).optional(),
55
56
  components: z.object({ securitySchemes: z.record(z.string(), z.unknown()).optional() }).optional(),
@@ -79,20 +80,31 @@ var WellKnownParsedSchema = z.object({
79
80
  function isRecord(value) {
80
81
  return value !== null && typeof value === "object" && !Array.isArray(value);
81
82
  }
82
- function hasSecurity(operation, scheme) {
83
- return operation.security?.some((s) => scheme in s) ?? false;
84
- }
85
- function has402Response(operation) {
86
- return Boolean(operation.responses?.["402"]);
83
+ function resolveSecurityFlags(requirements, securitySchemes) {
84
+ let hasApiKey = false;
85
+ let hasSiwx = false;
86
+ for (const requirement of requirements) {
87
+ for (const schemeName of Object.keys(requirement)) {
88
+ if (schemeName === "siwx") {
89
+ hasSiwx = true;
90
+ continue;
91
+ }
92
+ if (schemeName === "apiKey") {
93
+ hasApiKey = true;
94
+ continue;
95
+ }
96
+ const def = securitySchemes[schemeName];
97
+ if (isRecord(def) && def["type"] === "apiKey") hasApiKey = true;
98
+ }
99
+ }
100
+ return { hasApiKey, hasSiwx };
87
101
  }
88
- function inferAuthMode(operation) {
102
+ function inferAuthMode(operation, globalSecurity, securitySchemes) {
89
103
  const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
90
- const hasPayment = hasXPaymentInfo || has402Response(operation);
91
- const hasApiKey = hasSecurity(operation, "apiKey");
92
- const hasSiwx = hasSecurity(operation, "siwx");
93
- if (hasPayment && hasApiKey) return "apiKey+paid";
104
+ const effectiveSecurity = operation.security !== void 0 && operation.security.length > 0 ? operation.security : globalSecurity ?? [];
105
+ const { hasApiKey, hasSiwx } = resolveSecurityFlags(effectiveSecurity, securitySchemes ?? {});
106
+ if (hasXPaymentInfo && hasApiKey) return "apiKey+paid";
94
107
  if (hasXPaymentInfo) return "paid";
95
- if (hasPayment) return hasSiwx ? "siwx" : "paid";
96
108
  if (hasApiKey) return "apiKey";
97
109
  if (hasSiwx) return "siwx";
98
110
  return void 0;
@@ -112,10 +124,12 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
112
124
  var DEFAULT_MISSING_METHOD = "POST";
113
125
 
114
126
  // src/core/lib/url.ts
115
- function normalizeOrigin(target) {
127
+ function ensureProtocol(target) {
116
128
  const trimmed = target.trim();
117
- const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
118
- const url = new URL(withProtocol);
129
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
130
+ }
131
+ function normalizeOrigin(target) {
132
+ const url = new URL(ensureProtocol(target));
119
133
  url.pathname = "";
120
134
  url.search = "";
121
135
  url.hash = "";
@@ -154,7 +168,7 @@ function fetchSafe(url, init) {
154
168
  }
155
169
 
156
170
  // src/mmm-enabled.ts
157
- var isMmmEnabled = () => "1.0.1".includes("-mmm");
171
+ var isMmmEnabled = () => "1.0.2".includes("-mmm");
158
172
 
159
173
  // src/core/source/openapi/index.ts
160
174
  var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
@@ -163,15 +177,13 @@ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
163
177
  for (const httpMethod of [...HTTP_METHODS]) {
164
178
  const operation = pathItem[httpMethod.toLowerCase()];
165
179
  if (!operation) continue;
166
- const authMode = inferAuthMode(operation) ?? void 0;
180
+ const authMode = inferAuthMode(operation, doc.security, doc.components?.securitySchemes) ?? void 0;
167
181
  if (!authMode) continue;
168
182
  const p = operation["x-payment-info"];
169
183
  const protocols = (p?.protocols ?? []).filter(
170
184
  (proto) => proto !== "mpp" || isMmmEnabled()
171
185
  );
172
- if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
173
- if (authMode === "paid" && protocols.length === 0) continue;
174
- const pricing = authMode === "paid" && p ? {
186
+ const pricing = (authMode === "paid" || authMode === "apiKey+paid") && p ? {
175
187
  pricingMode: p.pricingMode,
176
188
  ...p.price ? { price: p.price } : {},
177
189
  ...p.minPrice ? { minPrice: p.minPrice } : {},
@@ -959,12 +971,12 @@ function getAdvisoriesForProbe(probe, path) {
959
971
  });
960
972
  }
961
973
  async function checkEndpointSchema(options) {
962
- const endpoint = new URL(options.url);
974
+ const endpoint = new URL(ensureProtocol(options.url));
963
975
  const origin = normalizeOrigin(endpoint.origin);
964
976
  const path = normalizePath(endpoint.pathname || "/");
965
977
  if (options.sampleInputBody !== void 0) {
966
978
  const probeResult2 = await getProbe(
967
- options.url,
979
+ endpoint.href,
968
980
  options.headers,
969
981
  options.signal,
970
982
  options.sampleInputBody
@@ -989,7 +1001,7 @@ async function checkEndpointSchema(options) {
989
1001
  if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
990
1002
  return { found: false, origin, path, cause: "not_found" };
991
1003
  }
992
- const probeResult = await getProbe(options.url, options.headers, options.signal);
1004
+ const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
993
1005
  if (probeResult.isErr()) {
994
1006
  return {
995
1007
  found: false,
package/dist/index.cjs CHANGED
@@ -97,6 +97,7 @@ var OpenApiDocSchema = import_zod.z.object({
97
97
  description: import_zod.z.string().optional(),
98
98
  guidance: import_zod.z.string().optional()
99
99
  }),
100
+ security: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))).optional(),
100
101
  servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string() })).optional(),
101
102
  tags: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string() })).optional(),
102
103
  components: import_zod.z.object({ securitySchemes: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional() }).optional(),
@@ -126,20 +127,31 @@ var WellKnownParsedSchema = import_zod.z.object({
126
127
  function isRecord(value) {
127
128
  return value !== null && typeof value === "object" && !Array.isArray(value);
128
129
  }
129
- function hasSecurity(operation, scheme) {
130
- return operation.security?.some((s) => scheme in s) ?? false;
131
- }
132
- function has402Response(operation) {
133
- return Boolean(operation.responses?.["402"]);
130
+ function resolveSecurityFlags(requirements, securitySchemes) {
131
+ let hasApiKey = false;
132
+ let hasSiwx = false;
133
+ for (const requirement of requirements) {
134
+ for (const schemeName of Object.keys(requirement)) {
135
+ if (schemeName === "siwx") {
136
+ hasSiwx = true;
137
+ continue;
138
+ }
139
+ if (schemeName === "apiKey") {
140
+ hasApiKey = true;
141
+ continue;
142
+ }
143
+ const def = securitySchemes[schemeName];
144
+ if (isRecord(def) && def["type"] === "apiKey") hasApiKey = true;
145
+ }
146
+ }
147
+ return { hasApiKey, hasSiwx };
134
148
  }
135
- function inferAuthMode(operation) {
149
+ function inferAuthMode(operation, globalSecurity, securitySchemes) {
136
150
  const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
137
- const hasPayment = hasXPaymentInfo || has402Response(operation);
138
- const hasApiKey = hasSecurity(operation, "apiKey");
139
- const hasSiwx = hasSecurity(operation, "siwx");
140
- if (hasPayment && hasApiKey) return "apiKey+paid";
151
+ const effectiveSecurity = operation.security !== void 0 && operation.security.length > 0 ? operation.security : globalSecurity ?? [];
152
+ const { hasApiKey, hasSiwx } = resolveSecurityFlags(effectiveSecurity, securitySchemes ?? {});
153
+ if (hasXPaymentInfo && hasApiKey) return "apiKey+paid";
141
154
  if (hasXPaymentInfo) return "paid";
142
- if (hasPayment) return hasSiwx ? "siwx" : "paid";
143
155
  if (hasApiKey) return "apiKey";
144
156
  if (hasSiwx) return "siwx";
145
157
  return void 0;
@@ -159,10 +171,12 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
159
171
  var DEFAULT_MISSING_METHOD = "POST";
160
172
 
161
173
  // src/core/lib/url.ts
162
- function normalizeOrigin(target) {
174
+ function ensureProtocol(target) {
163
175
  const trimmed = target.trim();
164
- const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
165
- const url = new URL(withProtocol);
176
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
177
+ }
178
+ function normalizeOrigin(target) {
179
+ const url = new URL(ensureProtocol(target));
166
180
  url.pathname = "";
167
181
  url.search = "";
168
182
  url.hash = "";
@@ -201,7 +215,7 @@ function fetchSafe(url, init) {
201
215
  }
202
216
 
203
217
  // src/mmm-enabled.ts
204
- var isMmmEnabled = () => "1.0.1".includes("-mmm");
218
+ var isMmmEnabled = () => "1.0.2".includes("-mmm");
205
219
 
206
220
  // src/core/source/openapi/index.ts
207
221
  var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
@@ -210,15 +224,13 @@ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
210
224
  for (const httpMethod of [...HTTP_METHODS]) {
211
225
  const operation = pathItem[httpMethod.toLowerCase()];
212
226
  if (!operation) continue;
213
- const authMode = inferAuthMode(operation) ?? void 0;
227
+ const authMode = inferAuthMode(operation, doc.security, doc.components?.securitySchemes) ?? void 0;
214
228
  if (!authMode) continue;
215
229
  const p = operation["x-payment-info"];
216
230
  const protocols = (p?.protocols ?? []).filter(
217
231
  (proto) => proto !== "mpp" || isMmmEnabled()
218
232
  );
219
- if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
220
- if (authMode === "paid" && protocols.length === 0) continue;
221
- const pricing = authMode === "paid" && p ? {
233
+ const pricing = (authMode === "paid" || authMode === "apiKey+paid") && p ? {
222
234
  pricingMode: p.pricingMode,
223
235
  ...p.price ? { price: p.price } : {},
224
236
  ...p.minPrice ? { minPrice: p.minPrice } : {},
@@ -1162,12 +1174,12 @@ function getAdvisoriesForProbe(probe, path) {
1162
1174
  });
1163
1175
  }
1164
1176
  async function checkEndpointSchema(options) {
1165
- const endpoint = new URL(options.url);
1177
+ const endpoint = new URL(ensureProtocol(options.url));
1166
1178
  const origin = normalizeOrigin(endpoint.origin);
1167
1179
  const path = normalizePath(endpoint.pathname || "/");
1168
1180
  if (options.sampleInputBody !== void 0) {
1169
1181
  const probeResult2 = await getProbe(
1170
- options.url,
1182
+ endpoint.href,
1171
1183
  options.headers,
1172
1184
  options.signal,
1173
1185
  options.sampleInputBody
@@ -1192,7 +1204,7 @@ async function checkEndpointSchema(options) {
1192
1204
  if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
1193
1205
  return { found: false, origin, path, cause: "not_found" };
1194
1206
  }
1195
- const probeResult = await getProbe(options.url, options.headers, options.signal);
1207
+ const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
1196
1208
  if (probeResult.isErr()) {
1197
1209
  return {
1198
1210
  found: false,
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ var OpenApiDocSchema = z.object({
50
50
  description: z.string().optional(),
51
51
  guidance: z.string().optional()
52
52
  }),
53
+ security: z.array(z.record(z.string(), z.array(z.string()))).optional(),
53
54
  servers: z.array(z.object({ url: z.string() })).optional(),
54
55
  tags: z.array(z.object({ name: z.string() })).optional(),
55
56
  components: z.object({ securitySchemes: z.record(z.string(), z.unknown()).optional() }).optional(),
@@ -79,20 +80,31 @@ var WellKnownParsedSchema = z.object({
79
80
  function isRecord(value) {
80
81
  return value !== null && typeof value === "object" && !Array.isArray(value);
81
82
  }
82
- function hasSecurity(operation, scheme) {
83
- return operation.security?.some((s) => scheme in s) ?? false;
84
- }
85
- function has402Response(operation) {
86
- return Boolean(operation.responses?.["402"]);
83
+ function resolveSecurityFlags(requirements, securitySchemes) {
84
+ let hasApiKey = false;
85
+ let hasSiwx = false;
86
+ for (const requirement of requirements) {
87
+ for (const schemeName of Object.keys(requirement)) {
88
+ if (schemeName === "siwx") {
89
+ hasSiwx = true;
90
+ continue;
91
+ }
92
+ if (schemeName === "apiKey") {
93
+ hasApiKey = true;
94
+ continue;
95
+ }
96
+ const def = securitySchemes[schemeName];
97
+ if (isRecord(def) && def["type"] === "apiKey") hasApiKey = true;
98
+ }
99
+ }
100
+ return { hasApiKey, hasSiwx };
87
101
  }
88
- function inferAuthMode(operation) {
102
+ function inferAuthMode(operation, globalSecurity, securitySchemes) {
89
103
  const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
90
- const hasPayment = hasXPaymentInfo || has402Response(operation);
91
- const hasApiKey = hasSecurity(operation, "apiKey");
92
- const hasSiwx = hasSecurity(operation, "siwx");
93
- if (hasPayment && hasApiKey) return "apiKey+paid";
104
+ const effectiveSecurity = operation.security !== void 0 && operation.security.length > 0 ? operation.security : globalSecurity ?? [];
105
+ const { hasApiKey, hasSiwx } = resolveSecurityFlags(effectiveSecurity, securitySchemes ?? {});
106
+ if (hasXPaymentInfo && hasApiKey) return "apiKey+paid";
94
107
  if (hasXPaymentInfo) return "paid";
95
- if (hasPayment) return hasSiwx ? "siwx" : "paid";
96
108
  if (hasApiKey) return "apiKey";
97
109
  if (hasSiwx) return "siwx";
98
110
  return void 0;
@@ -112,10 +124,12 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
112
124
  var DEFAULT_MISSING_METHOD = "POST";
113
125
 
114
126
  // src/core/lib/url.ts
115
- function normalizeOrigin(target) {
127
+ function ensureProtocol(target) {
116
128
  const trimmed = target.trim();
117
- const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
118
- const url = new URL(withProtocol);
129
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
130
+ }
131
+ function normalizeOrigin(target) {
132
+ const url = new URL(ensureProtocol(target));
119
133
  url.pathname = "";
120
134
  url.search = "";
121
135
  url.hash = "";
@@ -154,7 +168,7 @@ function fetchSafe(url, init) {
154
168
  }
155
169
 
156
170
  // src/mmm-enabled.ts
157
- var isMmmEnabled = () => "1.0.1".includes("-mmm");
171
+ var isMmmEnabled = () => "1.0.2".includes("-mmm");
158
172
 
159
173
  // src/core/source/openapi/index.ts
160
174
  var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
@@ -163,15 +177,13 @@ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
163
177
  for (const httpMethod of [...HTTP_METHODS]) {
164
178
  const operation = pathItem[httpMethod.toLowerCase()];
165
179
  if (!operation) continue;
166
- const authMode = inferAuthMode(operation) ?? void 0;
180
+ const authMode = inferAuthMode(operation, doc.security, doc.components?.securitySchemes) ?? void 0;
167
181
  if (!authMode) continue;
168
182
  const p = operation["x-payment-info"];
169
183
  const protocols = (p?.protocols ?? []).filter(
170
184
  (proto) => proto !== "mpp" || isMmmEnabled()
171
185
  );
172
- if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
173
- if (authMode === "paid" && protocols.length === 0) continue;
174
- const pricing = authMode === "paid" && p ? {
186
+ const pricing = (authMode === "paid" || authMode === "apiKey+paid") && p ? {
175
187
  pricingMode: p.pricingMode,
176
188
  ...p.price ? { price: p.price } : {},
177
189
  ...p.minPrice ? { minPrice: p.minPrice } : {},
@@ -1115,12 +1127,12 @@ function getAdvisoriesForProbe(probe, path) {
1115
1127
  });
1116
1128
  }
1117
1129
  async function checkEndpointSchema(options) {
1118
- const endpoint = new URL(options.url);
1130
+ const endpoint = new URL(ensureProtocol(options.url));
1119
1131
  const origin = normalizeOrigin(endpoint.origin);
1120
1132
  const path = normalizePath(endpoint.pathname || "/");
1121
1133
  if (options.sampleInputBody !== void 0) {
1122
1134
  const probeResult2 = await getProbe(
1123
- options.url,
1135
+ endpoint.href,
1124
1136
  options.headers,
1125
1137
  options.signal,
1126
1138
  options.sampleInputBody
@@ -1145,7 +1157,7 @@ async function checkEndpointSchema(options) {
1145
1157
  if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
1146
1158
  return { found: false, origin, path, cause: "not_found" };
1147
1159
  }
1148
- const probeResult = await getProbe(options.url, options.headers, options.signal);
1160
+ const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
1149
1161
  if (probeResult.isErr()) {
1150
1162
  return {
1151
1163
  found: false,
package/dist/schemas.cjs CHANGED
@@ -76,6 +76,7 @@ var OpenApiDocSchema = import_zod.z.object({
76
76
  description: import_zod.z.string().optional(),
77
77
  guidance: import_zod.z.string().optional()
78
78
  }),
79
+ security: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))).optional(),
79
80
  servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string() })).optional(),
80
81
  tags: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string() })).optional(),
81
82
  components: import_zod.z.object({ securitySchemes: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional() }).optional(),
@@ -300,6 +300,7 @@ declare const OpenApiDocSchema: z.ZodObject<{
300
300
  description: z.ZodOptional<z.ZodString>;
301
301
  guidance: z.ZodOptional<z.ZodString>;
302
302
  }, z.core.$strip>;
303
+ security: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
303
304
  servers: z.ZodOptional<z.ZodArray<z.ZodObject<{
304
305
  url: z.ZodString;
305
306
  }, z.core.$strip>>>;
package/dist/schemas.d.ts CHANGED
@@ -300,6 +300,7 @@ declare const OpenApiDocSchema: z.ZodObject<{
300
300
  description: z.ZodOptional<z.ZodString>;
301
301
  guidance: z.ZodOptional<z.ZodString>;
302
302
  }, z.core.$strip>;
303
+ security: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
303
304
  servers: z.ZodOptional<z.ZodArray<z.ZodObject<{
304
305
  url: z.ZodString;
305
306
  }, z.core.$strip>>>;
package/dist/schemas.js CHANGED
@@ -47,6 +47,7 @@ var OpenApiDocSchema = z.object({
47
47
  description: z.string().optional(),
48
48
  guidance: z.string().optional()
49
49
  }),
50
+ security: z.array(z.record(z.string(), z.array(z.string()))).optional(),
50
51
  servers: z.array(z.object({ url: z.string() })).optional(),
51
52
  tags: z.array(z.object({ name: z.string() })).optional(),
52
53
  components: z.object({ securitySchemes: z.record(z.string(), z.unknown()).optional() }).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/discovery",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Canonical OpenAPI-first discovery runtime for the agentcash ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",