@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.
- package/CHANGELOG.md +50 -0
- package/CITATION.cff +30 -30
- package/README.md +144 -142
- package/bin/cli.js +175 -172
- package/package.json +69 -64
- package/src/checks/04-mcp-exposure.js +165 -104
- package/src/checks/05-agentic-commerce.js +152 -85
- package/src/generators.js +228 -174
- package/src/index.js +127 -126
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (!
|
|
42
|
-
findings.push({
|
|
43
|
-
level: '
|
|
44
|
-
msg: '
|
|
45
|
-
});
|
|
46
|
-
return { score:
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
findings.push({
|
|
52
|
-
level: '
|
|
53
|
-
msg:
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
}
|