Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2f90047
Accept MPP method tempo (push mode) when MPP_TEMPO_RECIPIENT is set
claude Oct 4, 2026
6ada795
Clearer /api/explain description for agents
claude Oct 5, 2026
e2c21e4
Merge remote branch (already merged history)
claude Oct 5, 2026
57e398c
Add glama.json to claim the Glama listing
claude Oct 5, 2026
88577ee
Merge remote branch (already merged history)
claude Oct 5, 2026
2eeeeec
MCP registry: clearer description (says when an agent would use it)
claude Oct 5, 2026
ce242d7
Merge remote branch (already merged history)
claude Oct 5, 2026
71f6a3e
Homepage: a row of prices right under the intro
claude Oct 6, 2026
fd7ae48
Merge remote branch (already merged history)
claude Oct 6, 2026
193799b
Usage log: record the network a refused payment was tried on
claude Oct 6, 2026
2d888e4
Merge branch 'claude/x402-agents-solana-payments-nceg9b' of https://g…
claude Oct 6, 2026
322505c
Pay in RLUSD on the XRP Ledger (x402 exact, xrpl:0)
claude Oct 7, 2026
aa35a9d
Merge branch 'claude/x402-agents-solana-payments-nceg9b' of https://g…
claude Oct 7, 2026
633f050
XRPL: also accept payers built on t54's SDK
claude Oct 7, 2026
3033055
Merge remote-tracking branch 'origin/main' into claude/x402-agents-so…
claude Oct 7, 2026
fdab3b3
SEO: canonical tag, robots.txt and sitemap.xml for the homepage
claude Oct 7, 2026
0027283
Merge branch 'claude/x402-agents-solana-payments-nceg9b' of https://g…
claude Oct 7, 2026
823b132
CI: run the tests on every pull request and on main
claude Oct 7, 2026
60e2caa
Merge branch 'claude/x402-agents-solana-payments-nceg9b' of https://g…
claude Oct 7, 2026
d81e01e
Usage log: record why an MPP credential was refused
claude Oct 7, 2026
7a491d3
Merge branch 'claude/x402-agents-solana-payments-nceg9b' of https://g…
claude Oct 7, 2026
84c2f4e
Pay in USDC on Algorand (ALGORAND_PAY_TO)
claude Oct 8, 2026
a6cec2a
Merge remote branch
claude Oct 8, 2026
aa499c8
Name the seller in the 402: x402-merchant extension
claude Oct 9, 2026
7ec7ce1
Merge remote branch (already in main)
claude Oct 9, 2026
9c7b5d0
MCP: output schemas and structured content on every tool
claude Oct 9, 2026
3fdf50f
Merge remote branch (already in main)
claude Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion feedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// submit(input, { ip, userAgent, via }) the same for an MCP tool; { ok, status, body }
// mcpTool name, title, description and JSON schema for the MCP tool
// mcpShape(z) the input as a zod shape, for McpServer.registerTool
// mcpOutputShape(z) the answer to a received report as a zod object (outputSchema)
// mcpCall(args, { ip, userAgent }) the tool's CallToolResult
// The usage log's own MCP describer should skip the "feedback" tool: submit()
// already records it, with the full message.
Expand Down Expand Up @@ -141,13 +142,23 @@ function createFeedback({ service, record = () => {}, agentOf = (ua) => String(u

function mcpCall(args, { ip, userAgent } = {}) {
const out = submit(args, { ip, userAgent, via: 'mcp' });
return { content: [{ type: 'text', text: JSON.stringify(out.body) }], ...(out.ok ? {} : { isError: true }) };
return { content: [{ type: 'text', text: JSON.stringify(out.body) }], ...(out.ok ? { structuredContent: out.body } : { isError: true }) };
}

// The answer to a report that was received, as a zod object (the tool's outputSchema).
const mcpOutputShape = (z) => z.object({
received: z.boolean().describe('true: a person will read it'),
id: z.string().describe('Report id, to refer to it later'),
service: z.string().optional(),
type: z.string().optional(),
note: z.string().optional(),
}).passthrough();

return {
router,
submit,
mcpShape,
mcpOutputShape,
mcpCall,
mcpTool: { name: 'feedback', title: 'Send feedback (free)', description: DESCRIPTION, inputSchema: SCHEMA },
};
Expand Down
26 changes: 19 additions & 7 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,17 @@ app.post('/api/demo-explain', async (req, res) => {
// --- Free MCP tools, rate-limited (see /api/check-wallet and /api/explain
// above for the paid HTTP equivalents) ---

// What both tools answer: the verdict and the explanation, as text ("VERDICT: explanation", which the usage log
// reads) and as structuredContent matching this schema. Extra fields are allowed, so new ones never break a call.
const VERDICT_OUTPUT = z.object({
verdict: z.string().describe('SAFE, CAUTION or RISK'),
explanation: z.string().describe('Plain-language explanation of what the approval or payload allows')
}).passthrough();
const verdictResult = ({ verdict, explanation }) => ({
content: [{ type: 'text', text: `${verdict}: ${explanation}` }],
structuredContent: { verdict, explanation }
});

function buildMcpServer() {
const server = new McpServer({ name: 'plaintext-wallet-checker', version: '1.0.0' });

Expand All @@ -602,12 +613,12 @@ function buildMcpServer() {
address: z.string().regex(ADDRESS_RE).describe('EVM wallet address to check, e.g. 0x...'),
chain: z.enum(Object.keys(CHAINS)).optional().describe('Chain to check approvals on (default: ethereum)'),
kind: z.enum(['token', 'nft']).optional().describe('Approval type to check (default: token)')
})
}),
outputSchema: VERDICT_OUTPUT
},
async ({ address, chain, kind }) => {
try {
const result = await getWalletVerdict({ address, chain, kind });
return { content: [{ type: 'text', text: `${result.verdict}: ${result.explanation}` }] };
return verdictResult(await getWalletVerdict({ address, chain, kind }));
} catch (err) {
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
}
Expand All @@ -621,16 +632,16 @@ function buildMcpServer() {
description: "Explain an arbitrary approval/permission JSON payload in plain language, when you already have the data instead of needing an on-chain lookup.",
inputSchema: z.object({
data: z.record(z.string(), z.any()).describe('Arbitrary approval/permission JSON payload to explain')
})
}),
outputSchema: VERDICT_OUTPUT
},
async ({ data }) => {
try {
const size = JSON.stringify(data).length;
if (size > MAX_EXPLAIN_PAYLOAD_CHARS) {
return { content: [{ type: 'text', text: `Error: payload too large (${size} chars, max ${MAX_EXPLAIN_PAYLOAD_CHARS}).` }], isError: true };
}
const { verdict, explanation } = await explainPayload(data);
return { content: [{ type: 'text', text: `${verdict}: ${explanation}` }] };
return verdictResult(await explainPayload(data));
} catch (err) {
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
}
Expand All @@ -642,7 +653,8 @@ function buildMcpServer() {
{
title: feedback.mcpTool.title,
description: feedback.mcpTool.description,
inputSchema: z.object(feedback.mcpShape(z))
inputSchema: z.object(feedback.mcpShape(z)),
...(feedback.mcpOutputShape ? { outputSchema: feedback.mcpOutputShape(z) } : {})
},
async (args) => feedback.mcpCall(args, mcpCaller.getStore() || {})
);
Expand Down
Loading