@kirkelabs/agent-readiness-scan 0.1.0 → 0.2.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.
@@ -1,104 +1,165 @@
1
- /**
2
- * check 04 — MCP exposure
3
- *
4
- * Model Context Protocol (MCP, Anthropic) is the agent-tooling layer
5
- * for the customs-house era. A brand that exposes an MCP server card
6
- * at /.well-known/mcp/server-card.json is declaring "agents may act on
7
- * me with these scoped tools, behind this auth flow."
8
- *
9
- * NSA's May-2026 guidance recommends OAuth-protected-resource metadata
10
- * + PKCE/S256; we check for both as positive signals.
11
- */
12
-
13
- export const meta = {
14
- id: 'mcp-exposure',
15
- title: 'MCP exposure',
16
- weight: 7,
17
- why: 'Model Context Protocol is how a brand publishes the tools an AI agent can call on its behalf. Without a public server card, agents have nothing to invoke — the brand is read-only to the AI economy.',
18
- };
19
-
20
- export function run({ wellKnown }) {
21
- const findings = [];
22
- const detail = { card: { found: false, valid: false }, oauth: { found: false, pkce: false } };
23
-
24
- const card = wellKnown?.mcpServerCard;
25
- if (!card || !card.found) {
26
- findings.push({
27
- level: 'fail',
28
- msg: 'No `/.well-known/mcp/server-card.json` found. The brand exposes no agent-callable tools. AI agents can read but cannot act.',
29
- });
30
- return { score: 0, max: 7, findings, detail };
31
- }
32
-
33
- detail.card.found = true;
34
- let parsed;
35
- try {
36
- parsed = JSON.parse(card.content);
37
- detail.card.valid = typeof parsed === 'object' && parsed !== null;
38
- } catch {
39
- parsed = null;
40
- }
41
- if (!detail.card.valid) {
42
- findings.push({
43
- level: 'warn',
44
- msg: 'MCP server card present but does not parse as JSON. Agents discovering this surface would skip it.',
45
- });
46
- return { score: 2, max: 7, findings, detail };
47
- }
48
-
49
- let score = 4;
50
- findings.push({
51
- level: 'pass',
52
- msg: `MCP server card present and valid${parsed.name ? ` (\`${parsed.name}\`)` : ''}. Agents can discover the tool surface.`,
53
- });
54
- if (Array.isArray(parsed.tools) && parsed.tools.length > 0) {
55
- findings.push({
56
- level: 'pass',
57
- msg: `${parsed.tools.length} tool(s) declared in the server card.`,
58
- });
59
- } else {
60
- findings.push({
61
- level: 'warn',
62
- msg: 'Server card declares no `tools[]`. The card exists but has no callable surface for agents.',
63
- });
64
- score = 3;
65
- }
66
-
67
- // OAuth-protected-resource metadata (per NSA May-2026 guidance).
68
- const oauth = wellKnown?.oauthProtectedResource;
69
- if (oauth && oauth.found) {
70
- detail.oauth.found = true;
71
- score += 2;
72
- findings.push({
73
- level: 'pass',
74
- msg: '`/.well-known/oauth-protected-resource` discoverable — meets NSA May-2026 trust-boundary recommendation.',
75
- });
76
- try {
77
- const meta = JSON.parse(oauth.content);
78
- const auth = wellKnown?.oauthAuthorizationServer;
79
- if (auth && auth.found) {
80
- const authMeta = JSON.parse(auth.content);
81
- const methods = authMeta.code_challenge_methods_supported || [];
82
- if (Array.isArray(methods) && methods.includes('S256')) {
83
- detail.oauth.pkce = true;
84
- score += 1;
85
- findings.push({
86
- level: 'pass',
87
- msg: 'OAuth authorization server declares PKCE (S256) — scoped auth posture as recommended.',
88
- });
89
- }
90
- }
91
- // Suppress unused-var lint for `meta` if minimal.
92
- void meta;
93
- } catch {
94
- // ignore parse errors; partial credit already awarded.
95
- }
96
- } else {
97
- findings.push({
98
- level: 'warn',
99
- msg: 'No `/.well-known/oauth-protected-resource` metadata. NSA guidance recommends scoped OAuth gating before tool-call forwarding.',
100
- });
101
- }
102
-
103
- return { score: Math.min(score, 7), max: 7, findings, detail };
104
- }
1
+ /**
2
+ * check 04 — MCP exposure
3
+ *
4
+ * Model Context Protocol (MCP, Anthropic) is the agent-tooling layer
5
+ * for the customs-house era. A brand that exposes an MCP server card
6
+ * at /.well-known/mcp/server-card.json is declaring "agents may act on
7
+ * me with these scoped tools, behind this auth flow."
8
+ *
9
+ * NSA's May-2026 guidance recommends OAuth-protected-resource metadata
10
+ * + PKCE/S256; we check for both as positive signals.
11
+ *
12
+ * Scoped auth is scored standards-neutrally: a card can declare its
13
+ * trust boundary via EITHER OAuth-protected-resource (+PKCE) OR an Open
14
+ * Agent Access policy guard (+enforced policy). The two paths are
15
+ * mutually-reinforcing but capped — neither is privileged and the OAA
16
+ * path cannot push the dimension above the same ceiling OAuth reaches.
17
+ *
18
+ * Disclosure: Open Agent Access (OAA) is implemented by Kirke Labs, the
19
+ * author of this scanner. It is recognized here as one of several live
20
+ * scoped-auth postures, held to the same evidentiary bar as OAuth: a
21
+ * real, parseable artifact (an `authorization.model` declaration, plus a
22
+ * policy that genuinely requires an agent passport and gates calls).
23
+ */
24
+
25
+ export const meta = {
26
+ id: 'mcp-exposure',
27
+ title: 'MCP exposure',
28
+ weight: 7,
29
+ why: 'Model Context Protocol is how a brand publishes the tools an AI agent can call on its behalf. Without a public server card, agents have nothing to invoke — the brand is read-only to the AI economy.',
30
+ };
31
+
32
+ export function run({ wellKnown }) {
33
+ const findings = [];
34
+ const detail = {
35
+ card: { found: false, valid: false },
36
+ oauth: { found: false, pkce: false },
37
+ oaaGuard: { model: false, enforced: false },
38
+ };
39
+
40
+ const card = wellKnown?.mcpServerCard;
41
+ if (!card || !card.found) {
42
+ findings.push({
43
+ level: 'fail',
44
+ msg: 'No `/.well-known/mcp/server-card.json` found. The brand exposes no agent-callable tools. AI agents can read but cannot act.',
45
+ });
46
+ return { score: 0, max: 7, findings, detail };
47
+ }
48
+
49
+ detail.card.found = true;
50
+ let parsed;
51
+ try {
52
+ parsed = JSON.parse(card.content);
53
+ detail.card.valid = typeof parsed === 'object' && parsed !== null;
54
+ } catch {
55
+ parsed = null;
56
+ }
57
+ if (!detail.card.valid) {
58
+ findings.push({
59
+ level: 'warn',
60
+ msg: 'MCP server card present but does not parse as JSON. Agents discovering this surface would skip it.',
61
+ });
62
+ return { score: 2, max: 7, findings, detail };
63
+ }
64
+
65
+ let score = 4;
66
+ findings.push({
67
+ level: 'pass',
68
+ msg: `MCP server card present and valid${parsed.name ? ` (\`${parsed.name}\`)` : ''}. Agents can discover the tool surface.`,
69
+ });
70
+ if (Array.isArray(parsed.tools) && parsed.tools.length > 0) {
71
+ findings.push({
72
+ level: 'pass',
73
+ msg: `${parsed.tools.length} tool(s) declared in the server card.`,
74
+ });
75
+ } else {
76
+ findings.push({
77
+ level: 'warn',
78
+ msg: 'Server card declares no `tools[]`. The card exists but has no callable surface for agents.',
79
+ });
80
+ score = 3;
81
+ }
82
+
83
+ // --- Scoped-auth posture: OAuth path OR Open Agent Access path. ---
84
+
85
+ // OAuth-protected-resource metadata (per NSA May-2026 guidance).
86
+ const oauth = wellKnown?.oauthProtectedResource;
87
+ if (oauth && oauth.found) {
88
+ detail.oauth.found = true;
89
+ score += 2;
90
+ findings.push({
91
+ level: 'pass',
92
+ msg: '`/.well-known/oauth-protected-resource` discoverable — meets NSA May-2026 trust-boundary recommendation.',
93
+ });
94
+ try {
95
+ const meta = JSON.parse(oauth.content);
96
+ const auth = wellKnown?.oauthAuthorizationServer;
97
+ if (auth && auth.found) {
98
+ const authMeta = JSON.parse(auth.content);
99
+ const methods = authMeta.code_challenge_methods_supported || [];
100
+ if (Array.isArray(methods) && methods.includes('S256')) {
101
+ detail.oauth.pkce = true;
102
+ score += 1;
103
+ findings.push({
104
+ level: 'pass',
105
+ msg: 'OAuth authorization server declares PKCE (S256) — scoped auth posture as recommended.',
106
+ });
107
+ }
108
+ }
109
+ // Suppress unused-var lint for `meta` if minimal.
110
+ void meta;
111
+ } catch {
112
+ // ignore parse errors; partial credit already awarded.
113
+ }
114
+ }
115
+
116
+ // Open Agent Access guard: per-tool authorization via a published policy.
117
+ // Functionally parallel to OAuth-protected-resource as a trust boundary.
118
+ const authz = parsed.authorization;
119
+ const hasOaaModel =
120
+ authz?.model === 'open-agent-access' || typeof authz?.policy === 'string';
121
+ if (hasOaaModel) {
122
+ detail.oaaGuard.model = true;
123
+ score += 2;
124
+ findings.push({
125
+ level: 'pass',
126
+ msg: 'Server card declares an Open Agent Access authorization model (scoped per-tool policy) — a trust-boundary posture parallel to OAuth-protected-resource.',
127
+ });
128
+ // Confirm the referenced policy genuinely enforces per-call gating:
129
+ // an agent passport is required AND at least one deny/charge decision.
130
+ const oaa = wellKnown?.agentAccess;
131
+ if (oaa && oaa.found) {
132
+ try {
133
+ const policy = JSON.parse(oaa.content);
134
+ const rules = Array.isArray(policy?.rules) ? policy.rules : [];
135
+ const gated = rules.some(
136
+ (r) => r?.decision === 'deny' || r?.decision === 'charge',
137
+ );
138
+ if (policy?.defaults?.requireAgentIdentity === true && gated) {
139
+ detail.oaaGuard.enforced = true;
140
+ score += 1;
141
+ findings.push({
142
+ level: 'pass',
143
+ msg: 'Referenced Open Agent Access policy enforces per-call gating (agent passport required + deny/charge decisioning).',
144
+ });
145
+ } else {
146
+ findings.push({
147
+ level: 'warn',
148
+ msg: 'Open Agent Access policy present but does not enforce per-call gating (needs `defaults.requireAgentIdentity: true` and a deny/charge rule).',
149
+ });
150
+ }
151
+ } catch {
152
+ // ignore parse errors; partial credit already awarded for the declared model.
153
+ }
154
+ }
155
+ }
156
+
157
+ if (!detail.oauth.found && !detail.oaaGuard.model) {
158
+ findings.push({
159
+ level: 'warn',
160
+ msg: 'No scoped-auth posture declared. Add `/.well-known/oauth-protected-resource` (OAuth, per NSA guidance) or an `authorization.model: "open-agent-access"` policy before tool-call forwarding.',
161
+ });
162
+ }
163
+
164
+ return { score: Math.min(score, 7), max: 7, findings, detail };
165
+ }
@@ -1,85 +1,152 @@
1
- /**
2
- * check 05 — Agentic-commerce manifests
3
- *
4
- * The customs declaration for commerce. The two live specs as of mid-2026:
5
- * - Agentic Commerce Protocol (OpenAI/Stripe): /.well-known/acp/manifest.json
6
- * - Google Universal Cart Protocol (UCP): /.well-known/ucp
7
- *
8
- * Either one is a credible declaration that an agent can transact on
9
- * the brand's behalf. Both is full coverage.
10
- */
11
-
12
- export const meta = {
13
- id: 'agentic-commerce',
14
- title: 'Agentic-commerce manifests',
15
- weight: 7,
16
- why: 'ACP (OpenAI/Stripe) and UCP (Google) are the two live 2026 protocols for agent-initiated checkout. Without a manifest at the canonical .well-known path, conversational-commerce surfaces have no place to bind to.',
17
- };
18
-
19
- export function run({ wellKnown }) {
20
- const findings = [];
21
- const detail = { acp: { found: false, valid: false }, ucp: { found: false, valid: false } };
22
-
23
- // ACP
24
- const acp = wellKnown?.acpManifest;
25
- if (acp && acp.found) {
26
- detail.acp.found = true;
27
- try {
28
- const parsed = JSON.parse(acp.content);
29
- detail.acp.valid = typeof parsed === 'object' && parsed !== null;
30
- if (detail.acp.valid) {
31
- findings.push({
32
- level: 'pass',
33
- msg: `Agentic Commerce Protocol manifest present at \`/.well-known/acp/manifest.json\`${parsed.version ? ` (version \`${parsed.version}\`)` : ''}.`,
34
- });
35
- } else {
36
- findings.push({ level: 'warn', msg: 'ACP manifest present but invalid JSON shape.' });
37
- }
38
- } catch {
39
- findings.push({ level: 'warn', msg: 'ACP manifest present but does not parse as JSON.' });
40
- }
41
- }
42
-
43
- // UCP
44
- const ucp = wellKnown?.ucp;
45
- if (ucp && ucp.found) {
46
- detail.ucp.found = true;
47
- try {
48
- const parsed = JSON.parse(ucp.content);
49
- detail.ucp.valid = typeof parsed === 'object' && parsed !== null;
50
- if (detail.ucp.valid) {
51
- findings.push({
52
- level: 'pass',
53
- msg: `Google Universal Cart manifest present at \`/.well-known/ucp\`${parsed.version ? ` (version \`${parsed.version}\`)` : ''}.`,
54
- });
55
- } else {
56
- findings.push({ level: 'warn', msg: 'UCP manifest present but invalid JSON shape.' });
57
- }
58
- } catch {
59
- findings.push({ level: 'warn', msg: 'UCP manifest present but does not parse as JSON.' });
60
- }
61
- }
62
-
63
- let score;
64
- if (detail.acp.valid && detail.ucp.valid) {
65
- score = 7;
66
- findings.push({
67
- level: 'pass',
68
- msg: 'Both ACP and UCP manifests declared. Cross-platform agent commerce is wired.',
69
- });
70
- } else if (detail.acp.valid || detail.ucp.valid) {
71
- score = 5;
72
- findings.push({
73
- level: 'warn',
74
- msg: 'One of the two live agentic-commerce protocols declared. Consider adding the other to widen agent-marketplace coverage.',
75
- });
76
- } else {
77
- score = 0;
78
- findings.push({
79
- level: 'fail',
80
- msg: 'Neither `/.well-known/acp/manifest.json` (OpenAI/Stripe) nor `/.well-known/ucp` (Google) present. The site cannot be transacted on by an AI agent.',
81
- });
82
- }
83
-
84
- return { score, max: 7, findings, detail };
85
- }
1
+ /**
2
+ * check 05 — Agentic-commerce manifests
3
+ *
4
+ * The customs declaration for commerce. The live, interoperable specs as
5
+ * of mid-2026, scored as equal-weight alternatives:
6
+ * - Agentic Commerce Protocol (OpenAI/Stripe): /.well-known/acp/manifest.json
7
+ * - Google Universal Cart Protocol (UCP): /.well-known/ucp
8
+ * - Open Agent Access + x402 (on Algorand): /.well-known/agent-access.json
9
+ *
10
+ * Any one is a credible declaration that an agent can transact on the
11
+ * brand's behalf; more than one is wider coverage. The three are treated
12
+ * as standards-neutral alternatives — no protocol is privileged and the
13
+ * scoring ladder is unchanged.
14
+ *
15
+ * Disclosure: Open Agent Access (OAA) is implemented by Kirke Labs, the
16
+ * author of this scanner. It is recognized here as ONE of several live
17
+ * standards, held to the same evidentiary bar as ACP and UCP: a real,
18
+ * parseable artifact at a canonical path that actually advertises a
19
+ * transactable capability (an x402 `charge` rule). Detection is additive;
20
+ * it does not raise the weight or reduce credit for ACP/UCP.
21
+ */
22
+
23
+ export const meta = {
24
+ id: 'agentic-commerce',
25
+ title: 'Agentic-commerce manifests',
26
+ weight: 7,
27
+ why: 'ACP (OpenAI/Stripe), UCP (Google), and Open Agent Access + x402 (Algorand) are live 2026 protocols for agent-initiated checkout. Without a manifest at a canonical .well-known path, conversational-commerce surfaces have no place to bind to.',
28
+ };
29
+
30
+ export function run({ wellKnown }) {
31
+ const findings = [];
32
+ const detail = {
33
+ acp: { found: false, valid: false },
34
+ ucp: { found: false, valid: false },
35
+ oaaX402: { found: false, valid: false },
36
+ };
37
+
38
+ // ACP (OpenAI/Stripe)
39
+ const acp = wellKnown?.acpManifest;
40
+ if (acp && acp.found) {
41
+ detail.acp.found = true;
42
+ try {
43
+ const parsed = JSON.parse(acp.content);
44
+ detail.acp.valid = typeof parsed === 'object' && parsed !== null;
45
+ if (detail.acp.valid) {
46
+ findings.push({
47
+ level: 'pass',
48
+ msg: `Agentic Commerce Protocol manifest present at \`/.well-known/acp/manifest.json\`${parsed.version ? ` (version \`${parsed.version}\`)` : ''}.`,
49
+ });
50
+ } else {
51
+ findings.push({
52
+ level: 'warn',
53
+ msg: 'ACP manifest present but invalid JSON shape.',
54
+ });
55
+ }
56
+ } catch {
57
+ findings.push({
58
+ level: 'warn',
59
+ msg: 'ACP manifest present but does not parse as JSON.',
60
+ });
61
+ }
62
+ }
63
+
64
+ // UCP (Google)
65
+ const ucp = wellKnown?.ucp;
66
+ if (ucp && ucp.found) {
67
+ detail.ucp.found = true;
68
+ try {
69
+ const parsed = JSON.parse(ucp.content);
70
+ detail.ucp.valid = typeof parsed === 'object' && parsed !== null;
71
+ if (detail.ucp.valid) {
72
+ findings.push({
73
+ level: 'pass',
74
+ msg: `Google Universal Cart manifest present at \`/.well-known/ucp\`${parsed.version ? ` (version \`${parsed.version}\`)` : ''}.`,
75
+ });
76
+ } else {
77
+ findings.push({
78
+ level: 'warn',
79
+ msg: 'UCP manifest present but invalid JSON shape.',
80
+ });
81
+ }
82
+ } catch {
83
+ findings.push({
84
+ level: 'warn',
85
+ msg: 'UCP manifest present but does not parse as JSON.',
86
+ });
87
+ }
88
+ }
89
+
90
+ // Open Agent Access + x402 (Algorand). Counts only when the policy
91
+ // advertises a transactable capability: a `charge` rule carrying an
92
+ // x402 payment object. payment.network / payment.settlement corroborate.
93
+ const oaa = wellKnown?.agentAccess;
94
+ if (oaa && oaa.found) {
95
+ detail.oaaX402.found = true;
96
+ try {
97
+ const parsed = JSON.parse(oaa.content);
98
+ const rules = Array.isArray(parsed?.rules) ? parsed.rules : [];
99
+ const chargeRule = rules.find(
100
+ (r) => r?.decision === 'charge' && r?.payment?.type === 'x402',
101
+ );
102
+ detail.oaaX402.valid = Boolean(chargeRule);
103
+ if (detail.oaaX402.valid) {
104
+ const network =
105
+ chargeRule?.payment?.network || chargeRule?.payment?.settlement || 'Algorand';
106
+ findings.push({
107
+ level: 'pass',
108
+ msg: `Open Agent Access policy with x402 payment terms present at \`/.well-known/agent-access.json\` — agent-initiated settlement on ${network}.`,
109
+ });
110
+ } else {
111
+ findings.push({
112
+ level: 'warn',
113
+ msg: 'Open Agent Access policy present but declares no x402 `charge` rule — not yet a transactable commerce signal.',
114
+ });
115
+ }
116
+ } catch {
117
+ findings.push({
118
+ level: 'warn',
119
+ msg: 'Open Agent Access policy present but does not parse as JSON.',
120
+ });
121
+ }
122
+ }
123
+
124
+ // Score on the count of DISTINCT valid declarations among {acp, ucp, oaaX402}.
125
+ // Ladder unchanged: >=2 -> 7 ; exactly 1 -> 5 ; 0 -> 0.
126
+ const validCount = [detail.acp.valid, detail.ucp.valid, detail.oaaX402.valid].filter(
127
+ Boolean,
128
+ ).length;
129
+
130
+ let score;
131
+ if (validCount >= 2) {
132
+ score = 7;
133
+ findings.push({
134
+ level: 'pass',
135
+ msg: 'Multiple agentic-commerce declarations present — cross-platform agent commerce is wired.',
136
+ });
137
+ } else if (validCount === 1) {
138
+ score = 5;
139
+ findings.push({
140
+ level: 'warn',
141
+ msg: 'One of the live agentic-commerce protocols declared (ACP, UCP, or Open Agent Access + x402). Consider adding another to widen agent-marketplace coverage.',
142
+ });
143
+ } else {
144
+ score = 0;
145
+ findings.push({
146
+ level: 'fail',
147
+ msg: 'None of `/.well-known/acp/manifest.json` (OpenAI/Stripe), `/.well-known/ucp` (Google), or `/.well-known/agent-access.json` (Open Agent Access + x402) present. The site cannot be transacted on by an AI agent.',
148
+ });
149
+ }
150
+
151
+ return { score, max: 7, findings, detail };
152
+ }