auxilo-mcp 0.9.1 → 0.9.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/mcp-server.js CHANGED
@@ -10,14 +10,19 @@ const {
10
10
  ListToolsRequestSchema,
11
11
  } = require('@modelcontextprotocol/sdk/types.js');
12
12
 
13
+ // Review-seamless: pure selection + chunking helpers shared with the CLI so the
14
+ // MCP dry run and the confirmed run use the SAME logic (lib/review.js ships in
15
+ // the npm package alongside this file).
16
+ const reviewLib = require('./lib/review.js');
17
+
13
18
  // Credential file reading — auto-configure base URL and API key
14
19
  const CRED_PATH = path.join(os.homedir(), '.auxilo', 'credentials.json');
15
20
  let credentials = {};
16
21
  try {
17
22
  credentials = JSON.parse(fs.readFileSync(CRED_PATH, 'utf8'));
18
23
  } catch { /* no credentials file — unauthenticated mode */ }
19
- // LW-17: default was a long-dead Conway sandbox URL fresh installs without
20
- // credentials.json pointed every tool call at it.
24
+ // LW-17: default was a long-dead sandbox URL on a retired host, so fresh installs
25
+ // without credentials.json pointed every tool call at it.
21
26
  const AUXILO_BASE = credentials.base_url || 'https://auxilo.io';
22
27
 
23
28
  function baseHeaders(extra = {}) {
@@ -55,8 +60,107 @@ function fenceUnlockResult(data) {
55
60
  };
56
61
  }
57
62
 
63
+ // AUD19-5: decide how the unlock handler presents a payment-required response.
64
+ // 402 = x402-first challenge (server >= AUD19-5: accepts[] merged with the
65
+ // options envelope, served cold). 401-with-options = legacy servers that only
66
+ // returned the options block — same meaning, no accepts[]. Returns null when
67
+ // the response is not a payment challenge (caller falls through to
68
+ // fenceUnlockResult). Pure (no I/O) — unit-tested.
69
+ function unlockPaymentRequired(status, data, http_endpoint) {
70
+ const isChallenge = status === 402 || (status === 401 && data && data.options);
71
+ if (!isChallenge) return null;
72
+ const price = data.accepts?.[0]?.maxAmountRequired
73
+ ? `$${(Number(data.accepts[0].maxAmountRequired) / 1_000_000).toFixed(4)}`
74
+ : (data.options?.x402_payment?.price_usd != null
75
+ ? `$${Number(data.options.x402_payment.price_usd).toFixed(4)}` : 'dynamic');
76
+ return {
77
+ status: 'payment_required',
78
+ cost: `${price} USDC on Base (set by contributor)`,
79
+ how_to_pay: 'Pass an x402 payment via the x_payment argument, or configure an API key with unlock credits (npx auxilo setup).',
80
+ http_endpoint,
81
+ payment_details: data,
82
+ };
83
+ }
84
+
85
+ // AUD19-7 (DASH/ROUTE): shape the status-and-routing response for
86
+ // auxilo_withdraw from a GET /account/earnings body. This tool reports
87
+ // balances and the payout path; it NEVER moves funds and never calls
88
+ // POST /withdraw. Pure (no I/O) — unit-tested.
89
+ function shapeWithdrawStatus(e) {
90
+ return {
91
+ payout_model: 'Earnings from on-chain-settled sales are paid to your linked wallet at sale time. Only the legacy accrued balance below is withdrawable.',
92
+ legacy_pending_balance_usd: e.pending_balance,
93
+ held_pending_terms_acceptance_usd: e.unassented_pending,
94
+ payouts_paused: e.payouts_paused,
95
+ how_to_withdraw_legacy_balance: e.payouts_paused
96
+ ? 'Withdrawals of the legacy balance are paused during the settlement migration. Your balance is recorded and payable under the Terms; watch https://auxilo.io/status for the reopen.'
97
+ : 'Withdraw from your dashboard: https://auxilo.io/dashboard (bank payout via Stripe; $0.50 minimum, $0.25 fee).',
98
+ get_paid_at_sale_time: 'auxilo_accept_terms -> auxilo_verify_wallet -> auxilo_link_wallet',
99
+ };
100
+ }
101
+
102
+ // AUD19-7: the verify-wallet surface legitimately needs only the wallet-
103
+ // verification flow. Forwarding the raw args object let a caller mint OTHER
104
+ // challenge types (/wallet/challenge honors action:'withdrawal') through an
105
+ // undocumented passthrough — closed: only wallet + signature ever travel, so
106
+ // the server's default (verification) action always applies. Pure — unit-tested.
107
+ function verifyWalletRequestBody(args) {
108
+ return {
109
+ wallet: args.wallet,
110
+ ...(args.signature && { signature: args.signature }),
111
+ };
112
+ }
113
+
114
+ // Review-seamless: build the approve_clean dry-run plan from a summary payload.
115
+ // Pure (no I/O) so the dry-run shape is unit-testable; the selection itself is
116
+ // reviewLib.selectForBulkApprove, the SAME function the CLI uses.
117
+ function planApproveClean(summary, opts = {}) {
118
+ const minQuality = Number.isFinite(opts.min_quality) ? opts.min_quality : reviewLib.DEFAULT_QUALITY_THRESHOLD;
119
+ const sel = reviewLib.selectForBulkApprove((summary && summary.items) || [], { mode: 'clean', minQuality });
120
+ const brief = (r) => ({ id: r.id, title: r.title, category: r.category, quality: r.quality });
121
+ return {
122
+ dry_run: true,
123
+ min_quality: minQuality,
124
+ pending_count: summary ? summary.pending_count : 0,
125
+ would_approve_count: sel.selected.length,
126
+ would_approve: sel.selected.map(brief),
127
+ excluded_flagged_count: sel.excluded_flagged.length,
128
+ excluded_flagged: sel.excluded_flagged.map((r) => ({ ...brief(r), flags: r.flags })),
129
+ excluded_low_quality_count: sel.excluded_low_quality.length,
130
+ excluded_unscored_count: sel.excluded_unscored.length,
131
+ next_step: sel.selected.length > 0
132
+ ? `Show the operator this list and count (${sel.selected.length}). Only after their explicit confirmation, call auxilo_review again with {action:"approve_clean", dry_run:false, confirm:true, expected_count:${sel.selected.length}}. Approved items become PUBLIC immediately.`
133
+ : 'Nothing qualifies at this threshold. No follow-up call needed.',
134
+ };
135
+ }
136
+
137
+ // Review-seamless: POST one confirmed decision batch through the counted bulk
138
+ // endpoint, chunked at the server cap. Used by approve/reject/approve_clean.
139
+ async function postBulkChunks(headers, decisions) {
140
+ const totals = { approved: 0, rejected: 0, idempotent: 0, failed: 0, results: [] };
141
+ for (const chunk of reviewLib.chunkDecisions(decisions)) {
142
+ const resp = await fetch(`${AUXILO_BASE}/account/pending/bulk`, {
143
+ method: 'POST',
144
+ headers,
145
+ body: JSON.stringify({ decisions: chunk, confirm_count: chunk.length }),
146
+ });
147
+ const data = await resp.json();
148
+ if (!resp.ok) {
149
+ totals.error = data.error || `HTTP ${resp.status}`;
150
+ totals.error_code = data.code;
151
+ return totals;
152
+ }
153
+ totals.approved += data.approved || 0;
154
+ totals.rejected += data.rejected || 0;
155
+ totals.idempotent += data.idempotent || 0;
156
+ totals.failed += data.failed || 0;
157
+ if (Array.isArray(data.results)) totals.results.push(...data.results);
158
+ }
159
+ return totals;
160
+ }
161
+
58
162
  const server = new Server(
59
- { name: 'auxilo', version: '0.9.1' },
163
+ { name: 'auxilo', version: '0.9.2' },
60
164
  {
61
165
  capabilities: { tools: {} },
62
166
  instructions: `You are connected to Auxilo, a knowledge marketplace where AI agents buy and sell operational learnings.
@@ -130,7 +234,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
130
234
  },
131
235
  {
132
236
  name: 'auxilo_contribute',
133
- description: 'Submit operational knowledge you discovered during this session to the Auxilo marketplace. WHEN TO USE: Call this AFTER you solve a non-trivial problem that required debugging, experimentation, or combining information from multiple sources. Call it when you discover undocumented behavior, a workaround, or a subtle edge case. Do NOT call it for trivial lookups or standard documentation answers. QUALITY GATE: Self-assess on Specificity, Actionability, Novelty, Completeness (1-5 each). Only submit if total >= 14/20, no dimension below 3. DEDUP: Search auxilo_knowledge first to avoid duplicates. SENSITIVITY (mandatory self-screen): never include secrets, credentials, API keys, PII, private filesystem paths, or proprietary/client business content — generalize to placeholders or omit; this is a PUBLIC marketplace. PRICING: Leave unlock_price unset to let the dynamic pricing engine calculate automatically (recommended). If setting manually: $0.05-$0.10 common techniques, $0.10-$1.00 specific solutions, $1.00-$10.00 novel discoveries, $10.00-$50.00 breakthroughs. Minimum $0.05, maximum $50.00. Free to submit — you earn 70% when others unlock.',
237
+ description: 'Submit operational knowledge you discovered during this session to the Auxilo marketplace. WHEN TO USE: Call this AFTER you solve a non-trivial problem that required debugging, experimentation, or combining information from multiple sources. Call it when you discover undocumented behavior, a workaround, or a subtle edge case. Do NOT call it for trivial lookups or standard documentation answers. QUALITY GATE: Self-assess on Specificity, Actionability, Novelty, Completeness (1-5 each) and ALWAYS include your scores in quality_self_assessment — a submission WITHOUT it is held for manual review instead of publishing seamlessly. Only submit if total >= 14/20, no dimension below 3 (the server quarantines below-floor submissions for review). DEDUP: Search auxilo_knowledge first to avoid duplicates. SENSITIVITY (mandatory self-screen): never include secrets, credentials, API keys, PII, private filesystem paths, or proprietary/client business content — generalize to placeholders or omit; this is a PUBLIC marketplace. PRICING: Leave unlock_price unset to let the dynamic pricing engine calculate automatically (recommended). If setting manually: $0.05-$0.10 common techniques, $0.10-$1.00 specific solutions, $1.00-$10.00 novel discoveries, $10.00-$50.00 breakthroughs. Minimum $0.05, maximum $50.00. Free to submit — you earn 70% when others unlock. If the result is status pending_review, follow its how_to_review instructions (self-approval via `auxilo review`, the dashboard queue, or GET /account/pending).',
134
238
  inputSchema: {
135
239
  type: 'object',
136
240
  properties: {
@@ -140,12 +244,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
140
244
  tags: { type: 'array', items: { type: 'string' }, description: 'Relevant keywords' },
141
245
  task_context: { type: 'string', description: 'What task were you performing?' },
142
246
  outcome: { type: 'string', enum: ['success', 'partial', 'failure', 'workaround'] },
247
+ quality_self_assessment: {
248
+ type: 'object',
249
+ description: 'Your quality self-assessment. ALWAYS include this — without it the submission is held for manual review. Score each dimension 1-5; total MUST equal their sum (server-verified). Submissions below the floor (total < 14 or any dimension < 3) are quarantined for review rather than published.',
250
+ properties: {
251
+ specificity: { type: 'integer', minimum: 1, maximum: 5, description: 'How precise and detailed? (1-5)' },
252
+ actionability: { type: 'integer', minimum: 1, maximum: 5, description: 'Can another agent directly use this? (1-5)' },
253
+ novelty: { type: 'integer', minimum: 1, maximum: 5, description: 'Non-obvious / would an LLM get it wrong? (1-5)' },
254
+ completeness: { type: 'integer', minimum: 1, maximum: 5, description: 'Full context, reproduction steps, caveats? (1-5)' },
255
+ total: { type: 'integer', minimum: 4, maximum: 20, description: 'Sum of the four dimensions (server rejects mismatched totals)' },
256
+ reasoning: { type: 'string', description: 'Optional: one-line justification for your scores' },
257
+ },
258
+ required: ['specificity', 'actionability', 'novelty', 'completeness', 'total'],
259
+ },
143
260
  contributor_wallet: { type: 'string', description: 'Your Base wallet (0x...) for revenue share' },
144
261
  unlock_price: { type: 'number', description: 'Price in USD to unlock this learning (min $0.05, default auto-calculated). Set higher for deep, high-value knowledge.' },
145
262
  contributor_agent: { type: 'string', description: 'Optional: identify yourself' },
146
263
  related_skills: { type: 'array', items: { type: 'string' }, description: 'Optional: related Auxilo skill IDs' },
147
264
  },
148
- required: ['title', 'body', 'category', 'tags', 'task_context', 'outcome'],
265
+ required: ['title', 'body', 'category', 'tags', 'task_context', 'outcome', 'quality_self_assessment'],
149
266
  },
150
267
  },
151
268
  {
@@ -166,7 +283,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
166
283
  },
167
284
  {
168
285
  name: 'auxilo_unlock',
169
- description: 'Unlock full learning content by ID. Price is set by the contributor (min $0.05 USDC). 70% goes to the contributor who shared this knowledge. Check unlock_price_usd in search results to see the cost before unlocking.',
286
+ description: 'Unlock full learning content by ID. Price is set by the contributor (min $0.05 USDC). 70% of the amount you pay goes to the contributor who shared this knowledge. Check unlock_price_usd in search results to see the cost before unlocking.',
170
287
  inputSchema: {
171
288
  type: 'object',
172
289
  properties: {
@@ -203,20 +320,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
203
320
  },
204
321
  {
205
322
  name: 'auxilo_withdraw',
206
- description: 'Request withdrawal of earned USDC. Requires a valid cryptographic signature of: "auxilo-withdraw-{wallet}-{amount}-{timestamp}". NOTE: withdrawals are TEMPORARILY PAUSED during the non-custodial settlement migrationthis call currently returns HTTP 503 with code "withdraw_paused_noncustodial_migration". Earned balances are safe and become payable on the new on-chain rail; there is nothing to retry until the pause lifts.',
323
+ description: 'Check how your earnings are paid out, and how to receive any legacy accrued balance. HOW PAYOUT WORKS NOW: earnings from sales settled on-chain are paid directly to your linked, verified wallet at the moment of salethere is no balance to withdraw for those, because Auxilo never holds your share. LEGACY BALANCE: earnings accrued before direct settlement sit in your account\'s pending balance; withdraw them from your dashboard at https://auxilo.io/dashboard (bank payout via Stripe; $0.50 minimum, $0.25 flat fee), where you sign in with the email on your account. This tool reports your current balances and payout status; it does not move funds. To be paid at sale time going forward: auxilo_accept_terms, then auxilo_verify_wallet, then auxilo_link_wallet.',
207
324
  inputSchema: {
208
325
  type: 'object',
209
326
  properties: {
210
- wallet: { type: 'string', description: 'Verified contributor wallet address (0x...)' },
211
- signature: { type: 'string', description: 'Signature of the withdrawal payload' },
212
- timestamp: { type: 'number', description: 'Unix timestamp in milliseconds (must be within 5 mins of server time)' }
327
+ session_token: { type: 'string', description: 'Optional JWT session token from /auth/verify. If omitted, your configured API key authenticates the account.' }
213
328
  },
214
- required: ['wallet', 'signature', 'timestamp']
329
+ required: []
215
330
  }
216
331
  },
217
332
  {
218
333
  name: 'auxilo_settlements',
219
- description: 'Check settlement history and processing status for withdrawals on a given wallet. Free.',
334
+ description: 'Check settlement history and processing status for a given wallet. Lists on-chain router settlements (per-sale receipts paid to your wallet at sale time), not only withdrawal history. Free.',
220
335
  inputSchema: {
221
336
  type: 'object',
222
337
  properties: {
@@ -238,11 +353,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
238
353
  },
239
354
  {
240
355
  name: 'auxilo_link_wallet',
241
- description: 'Link a verified wallet address to your Auxilo account. Authenticates with your configured API key automatically, or pass a session_token (JWT from magic link login). The wallet must have been previously verified via auxilo_verify_wallet. One wallet per account. Required to withdraw earnings. NOTE: you must first accept the current Terms via auxilo_accept_terms — linking a payout wallet triggers the §5.10 payment-collection agency and returns 403 TERMS_NOT_ACCEPTED until acceptance is on file.',
356
+ description: 'Link a verified wallet address to your Auxilo account. Authenticates with your configured API key automatically, or pass a session_token (JWT from magic link login). The wallet must have been previously verified via auxilo_verify_wallet. One wallet per account. Required to be paid at sale time on the on-chain settlement rail (and to receive any legacy balance on the USDC rail if it reopens). TWO-STEP FLOW (linking requires FRESH proof you control the wallet, bound to your account): (1) call with just the wallet — the server returns 401 link_signature_required with an EIP-712 challenge payload (single-use, 5-minute expiry, the account id is bound into the signed action string); (2) sign that exact typed-data payload with the wallet\'s private key and call again with the signature. A signature for another account\'s challenge will not verify. NOTE: you must first accept the current Terms via auxilo_accept_terms — linking a payout wallet triggers the §5.10 payment-collection agency and returns 403 TERMS_NOT_ACCEPTED until acceptance is on file. If the link adopts previously wallet-only submissions into your account, the response lists their ids (adopted_learning_ids).',
242
357
  inputSchema: {
243
358
  type: 'object',
244
359
  properties: {
245
360
  wallet: { type: 'string', description: 'Verified wallet address (0x...) to link to your account' },
361
+ signature: { type: 'string', description: 'EIP-712 signature of the link challenge, produced by the wallet\'s key. Omit on the first call to receive the challenge payload to sign.' },
246
362
  session_token: { type: 'string', description: 'Optional JWT session token from /auth/verify. If omitted, your configured API key authenticates the account.' },
247
363
  },
248
364
  required: ['wallet'],
@@ -250,7 +366,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
250
366
  },
251
367
  {
252
368
  name: 'auxilo_account_earnings',
253
- description: 'View earnings for your authenticated Auxilo account. Authenticates with your configured API key automatically, or pass a session_token (JWT). Returns total gross, contributor share, pending balance, total withdrawn, and whether withdrawal is available (can_withdraw). Free.',
369
+ description: 'View earnings for your authenticated Auxilo account. Authenticates with your configured API key automatically, or pass a session_token (JWT). Returns total gross, contributor share, pending balance, total withdrawn, whether withdrawal is available (can_withdraw), and held_pending_assent — undisbursable receipts recorded before you accepted the current Terms, released to your withdrawable balance when you accept via auxilo_accept_terms. Earnings from on-chain-settled sales are paid to your wallet at sale time and appear in settlement history, not in pending balance. Free.',
254
370
  inputSchema: {
255
371
  type: 'object',
256
372
  properties: {
@@ -273,56 +389,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
273
389
  },
274
390
  },
275
391
  {
276
- name: 'renderly_markdown',
277
- description: 'Convert any public URL to clean markdown. Strips navigation, ads, and boilerplate returns only the meaningful content. Costs $0.001 USDC via x402 or API key.',
392
+ name: 'auxilo_review',
393
+ description: 'Review YOUR OWN pending-review learnings (from background extraction) so they can be approved to the public marketplace or rejected to stay private. Account-scoped: only the authenticated account\'s own pending items are ever visible or affected. ACTIONS: "list" returns the triage summary (counts + compact rows with quality score and platform screen verdicts: injection, content sensitivity, near-duplicate). "approve" / "reject" apply explicit decisions to the ids you pass (the operator must have named or confirmed these items). "approve_clean" selects every item that passed ALL platform screens AND has quality >= min_quality (default 14/20); it is DRY-RUN BY DEFAULT and returns exactly what WOULD be approved. CONSENT CONTRACT: nothing goes public without the contributor\'s explicit approval. So before executing approve_clean you MUST show the operator the dry-run list and count and get their confirmation, then call again with dry_run:false, confirm:true, and expected_count set to the dry-run count. The server also enforces a counted-confirmation gate on every bulk call. Requires your configured API key (or session_token).',
278
394
  inputSchema: {
279
395
  type: 'object',
280
396
  properties: {
281
- url: { type: 'string', description: 'Public URL to convert to markdown' },
282
- x_payment: { type: 'string', description: 'x402 payment header' },
283
- },
284
- required: ['url'],
285
- },
286
- },
287
- {
288
- name: 'renderly_extract',
289
- description: 'Extract structured data from any public URL — title, description, headings, links, images, and meta tags. Costs $0.001 USDC via x402 or API key.',
290
- inputSchema: {
291
- type: 'object',
292
- properties: {
293
- url: { type: 'string', description: 'Public URL to extract structured data from' },
294
- x_payment: { type: 'string', description: 'x402 payment header' },
295
- },
296
- required: ['url'],
297
- },
298
- },
299
- {
300
- name: 'renderly_readable',
301
- description: 'Get plain readable text from any public URL — no markdown formatting, no HTML, just the content. Costs $0.0005 USDC via x402 or API key.',
302
- inputSchema: {
303
- type: 'object',
304
- properties: {
305
- url: { type: 'string', description: 'Public URL to extract readable text from' },
306
- x_payment: { type: 'string', description: 'x402 payment header' },
397
+ action: { type: 'string', enum: ['list', 'approve', 'reject', 'approve_clean'], description: 'What to do. Start with "list".' },
398
+ ids: { type: 'array', items: { type: 'string' }, description: 'Learning ids for action approve/reject. These must be items the operator explicitly chose.' },
399
+ reason: { type: 'string', description: 'Optional rejection reason (action reject; max 500 chars).' },
400
+ dry_run: { type: 'boolean', description: 'approve_clean only. Default TRUE: report what would be approved without changing anything. Set false only together with confirm:true and expected_count after the operator confirmed the dry-run list.' },
401
+ confirm: { type: 'boolean', description: 'approve_clean only. Must be exactly true to execute. Never set this without the operator\'s explicit go-ahead on the dry-run output.' },
402
+ expected_count: { type: 'number', description: 'approve_clean execute only. The count from the dry run, echoed back. If the live selection differs (queue changed), nothing is approved and a fresh dry run is returned.' },
403
+ min_quality: { type: 'number', description: 'approve_clean quality threshold 0-20 (default 14). 0 includes unscored items.' },
404
+ session_token: { type: 'string', description: 'Optional JWT session token from /auth/verify. If omitted, your configured API key authenticates the account.' },
307
405
  },
308
- required: ['url'],
406
+ required: ['action'],
309
407
  },
310
408
  },
311
- {
312
- name: 'renderly_llms_txt',
313
- description: 'Get the LLM-readable service description for Renderly — what it does, endpoints, and usage. Free.',
314
- inputSchema: { type: 'object', properties: {} },
315
- },
316
- {
317
- name: 'renderly_health',
318
- description: 'Check Renderly service health status. Free.',
319
- inputSchema: { type: 'object', properties: {} },
320
- },
321
- {
322
- name: 'renderly_pricing',
323
- description: 'Get Renderly pricing information for all endpoints. Free.',
324
- inputSchema: { type: 'object', properties: {} },
325
- },
326
409
  {
327
410
  name: 'get_stats',
328
411
  description: 'Get Auxilo registry statistics — catalog size, skill types, and query volume. Free.',
@@ -389,6 +472,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
389
472
  tags: args.tags,
390
473
  task_context: args.task_context,
391
474
  outcome: args.outcome,
475
+ // AUD19-4: pass the quality self-assessment through — without it the
476
+ // server can never seamless-publish (qualityPresent is false) and every
477
+ // MCP contribution lands pending_review with 'awaiting_quality'.
478
+ ...(args.quality_self_assessment && { quality_self_assessment: args.quality_self_assessment }),
392
479
  ...(args.contributor_wallet && { contributor_wallet: args.contributor_wallet }),
393
480
  unlock_price: args.unlock_price,
394
481
  contributor_agent: args.contributor_agent,
@@ -421,10 +508,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
421
508
 
422
509
  const resp = await fetch(`${AUXILO_BASE}/knowledge/${args.id}`, { headers });
423
510
  const data = await resp.json();
424
- if (resp.status === 402) {
425
- const price = data.accepts?.[0]?.maxAmountRequired ? `$${(Number(data.accepts[0].maxAmountRequired) / 1_000_000).toFixed(4)}` : 'dynamic';
426
- return text({ status: 'payment_required', cost: `${price} USDC on Base (set by contributor)`, http_endpoint: `${AUXILO_BASE}/knowledge/${args.id}`, payment_details: data });
427
- }
511
+ // AUD19-5: handle BOTH the 402-first challenge (server >= the fix,
512
+ // accepts[] served cold) and the legacy 401-with-options shape from
513
+ // older live servers same meaning, no accepts[].
514
+ const challenge = unlockPaymentRequired(resp.status, data, `${AUXILO_BASE}/knowledge/${args.id}`);
515
+ if (challenge) return text(challenge);
428
516
  // LW-3(a): fence the contributor body so the LLM treats it as data, not instructions.
429
517
  return text(fenceUnlockResult(data));
430
518
  }
@@ -440,33 +528,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
440
528
 
441
529
  case 'auxilo_verify_wallet': {
442
530
  const url = args.signature ? `${AUXILO_BASE}/wallet/verify` : `${AUXILO_BASE}/wallet/challenge`;
531
+ // AUD19-7: whitelist the request body — only wallet + signature travel.
532
+ // (Forwarding raw args let a caller mint a withdrawal-action challenge
533
+ // through an undocumented passthrough; the MCP surface only needs the
534
+ // default verification action.)
443
535
  const resp = await fetch(url, {
444
536
  method: 'POST',
445
537
  headers: baseHeaders(),
446
- body: JSON.stringify(args)
538
+ body: JSON.stringify(verifyWalletRequestBody(args))
447
539
  });
448
540
  return text(await resp.json());
449
541
  }
450
542
 
451
543
  case 'auxilo_withdraw': {
452
- const resp = await fetch(`${AUXILO_BASE}/withdraw`, {
453
- method: 'POST',
454
- headers: baseHeaders(),
455
- body: JSON.stringify(args)
544
+ // AUD19-7 (DASH/ROUTE): honest status-and-routing tool. Reads
545
+ // GET /account/earnings and reports balances + the real payout path.
546
+ // It never attempts a custodial withdrawal (no POST /withdraw) — new
547
+ // earnings settle to the linked wallet at sale time; the legacy
548
+ // balance exits via the dashboard (Stripe) rail.
549
+ const resp = await fetch(`${AUXILO_BASE}/account/earnings`, {
550
+ headers: baseHeaders(args.session_token ? { 'Authorization': `Bearer ${args.session_token}` } : {}),
456
551
  });
457
- const data = await resp.json();
458
- // R-01: the custodial USDC rail is paused during the non-custodial
459
- // migration and returns 503 withdraw_paused_noncustodial_migration.
460
- // Surface that as a plain, human-legible note instead of a raw error
461
- // blob so the agent doesn't treat it as a transient failure to retry.
462
- if (resp.status === 503 && data && data.code === 'withdraw_paused_noncustodial_migration') {
552
+ const e = await resp.json();
553
+ if (resp.status === 401) {
463
554
  return text({
464
- status: 'paused',
465
- message: 'Withdrawals are temporarily paused while Auxilo migrates to direct on-chain (non-custodial) settlement. Your earned balance is safe and will be payable on the new rail once the migration completes. This is expected — do not retry; nothing is wrong with your account or signature.',
466
- server_response: data,
555
+ error: 'Not authenticated. Run npx auxilo setup to create credentials, or pass session_token. Balances are account-scoped.',
556
+ server_response: e,
467
557
  });
468
558
  }
469
- return text(data);
559
+ if (!resp.ok) return text(e);
560
+ return text(shapeWithdrawStatus(e));
470
561
  }
471
562
 
472
563
  case 'auxilo_settlements': {
@@ -489,9 +580,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
489
580
  headers: baseHeaders(
490
581
  args.session_token ? { 'Authorization': `Bearer ${args.session_token}` } : {}
491
582
  ),
492
- body: JSON.stringify({ wallet: args.wallet }),
583
+ // HIGH-1: pass the link signature through when present (step 2 of the
584
+ // two-step link flow — see the tool description).
585
+ body: JSON.stringify({
586
+ wallet: args.wallet,
587
+ ...(args.signature && { signature: args.signature }),
588
+ }),
493
589
  });
494
- return text(await resp.json());
590
+ const data = await resp.json();
591
+ // HIGH-1: the challenge response is expected, not an error — surface it
592
+ // as the next step so the agent signs and calls again instead of
593
+ // treating the 401 as a failure.
594
+ if (resp.status === 401 && data && data.code === 'link_signature_required') {
595
+ return text({
596
+ status: 'signature_required',
597
+ message: 'Expected first-step response: sign the eip712 payload below with the wallet\'s private key (EIP-712 typed data — the account id is bound into the action string), then call auxilo_link_wallet again with the same wallet plus the signature. The challenge is single-use and expires in 5 minutes.',
598
+ server_response: data,
599
+ });
600
+ }
601
+ return text(data);
495
602
  }
496
603
 
497
604
  case 'auxilo_account_earnings': {
@@ -531,82 +638,72 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
531
638
  return text(await resp.json());
532
639
  }
533
640
 
534
- case 'renderly_markdown': {
535
- const headers = baseHeaders();
536
- if (args.x_payment) headers['X-Payment'] = args.x_payment;
641
+ case 'auxilo_review': {
642
+ // Same auth idiom as the other account tools: session JWT when
643
+ // provided, else the configured API key via baseHeaders().
644
+ const headers = baseHeaders(
645
+ args.session_token ? { 'Authorization': `Bearer ${args.session_token}` } : {}
646
+ );
537
647
 
538
- const resp = await fetch(`${AUXILO_BASE}/renderly/markdown`, {
539
- method: 'POST',
540
- headers,
541
- body: JSON.stringify({ url: args.url }),
542
- });
543
- const data = await resp.json();
544
- if (resp.status === 402) {
545
- return text({
546
- status: 'payment_required',
547
- cost: '$0.001 USDC on Base',
548
- http_endpoint: `${AUXILO_BASE}/renderly/markdown`,
549
- payment_details: data,
550
- });
648
+ if (args.action === 'list') {
649
+ const resp = await fetch(`${AUXILO_BASE}/account/pending/summary`, { headers });
650
+ return text(await resp.json());
551
651
  }
552
- return text(data);
553
- }
554
-
555
- case 'renderly_extract': {
556
- const headers = baseHeaders();
557
- if (args.x_payment) headers['X-Payment'] = args.x_payment;
558
652
 
559
- const resp = await fetch(`${AUXILO_BASE}/renderly/extract`, {
560
- method: 'POST',
561
- headers,
562
- body: JSON.stringify({ url: args.url }),
563
- });
564
- const data = await resp.json();
565
- if (resp.status === 402) {
566
- return text({
567
- status: 'payment_required',
568
- cost: '$0.001 USDC on Base',
569
- http_endpoint: `${AUXILO_BASE}/renderly/extract`,
570
- payment_details: data,
571
- });
653
+ if (args.action === 'approve' || args.action === 'reject') {
654
+ if (!Array.isArray(args.ids) || args.ids.length === 0) {
655
+ return text({ error: `action "${args.action}" requires a non-empty ids array. Run {action:"list"} first and pass the ids the operator chose.` });
656
+ }
657
+ const decisions = args.ids.map((id) => (args.action === 'reject' && args.reason)
658
+ ? { id, decision: 'reject', reason: args.reason }
659
+ : { id, decision: args.action });
660
+ const totals = await postBulkChunks(headers, decisions);
661
+ return text({ action: args.action, submitted: decisions.length, ...totals });
572
662
  }
573
- return text(data);
574
- }
575
663
 
576
- case 'renderly_readable': {
577
- const headers = baseHeaders();
578
- if (args.x_payment) headers['X-Payment'] = args.x_payment;
664
+ if (args.action === 'approve_clean') {
665
+ const summaryResp = await fetch(`${AUXILO_BASE}/account/pending/summary`, { headers });
666
+ const summary = await summaryResp.json();
667
+ if (!summaryResp.ok) return text(summary);
579
668
 
580
- const resp = await fetch(`${AUXILO_BASE}/renderly/readable`, {
581
- method: 'POST',
582
- headers,
583
- body: JSON.stringify({ url: args.url }),
584
- });
585
- const data = await resp.json();
586
- if (resp.status === 402) {
669
+ const plan = planApproveClean(summary, args);
670
+
671
+ // DRY RUN unless the operator-confirmed execute contract is complete:
672
+ // dry_run explicitly false AND confirm exactly true.
673
+ if (args.dry_run !== false || args.confirm !== true) {
674
+ return text(plan);
675
+ }
676
+
677
+ // Counted confirmation: the execute call must echo the dry-run count.
678
+ if (!Number.isInteger(args.expected_count)) {
679
+ return text({
680
+ error: 'expected_count is required to execute approve_clean: echo the would_approve_count from the dry run. This is the counted-confirmation gate.',
681
+ ...plan,
682
+ });
683
+ }
684
+ if (args.expected_count !== plan.would_approve_count) {
685
+ return text({
686
+ error: `Selection changed since the dry run (expected ${args.expected_count}, now ${plan.would_approve_count}). Nothing was approved. Re-run the dry run and re-confirm with the operator.`,
687
+ ...plan,
688
+ });
689
+ }
690
+ if (plan.would_approve_count === 0) {
691
+ return text({ action: 'approve_clean', approved: 0, message: 'Nothing qualifies at this threshold.' });
692
+ }
693
+
694
+ const decisions = plan.would_approve.map((r) => ({ id: r.id, decision: 'approve' }));
695
+ const totals = await postBulkChunks(headers, decisions);
587
696
  return text({
588
- status: 'payment_required',
589
- cost: '$0.0005 USDC on Base',
590
- http_endpoint: `${AUXILO_BASE}/renderly/readable`,
591
- payment_details: data,
697
+ action: 'approve_clean',
698
+ executed: true,
699
+ min_quality: plan.min_quality,
700
+ submitted: decisions.length,
701
+ ...totals,
702
+ note: 'Approved items are now PUBLIC on the marketplace. Screen-flagged and below-threshold items remain pending for individual review.',
592
703
  });
593
704
  }
594
- return text(data);
595
- }
596
705
 
597
- case 'renderly_llms_txt': {
598
- const resp = await fetch(`${AUXILO_BASE}/renderly/llms.txt`, { headers: baseHeaders() });
599
- return text(await resp.json());
600
- }
601
-
602
- case 'renderly_health': {
603
- const resp = await fetch(`${AUXILO_BASE}/renderly/health`, { headers: baseHeaders() });
604
- return text(await resp.json());
605
- }
606
-
607
- case 'renderly_pricing': {
608
- const resp = await fetch(`${AUXILO_BASE}/renderly/pricing`, { headers: baseHeaders() });
609
- return text(await resp.json());
706
+ return text({ error: `Unknown action: ${args.action}. Use list, approve, reject, or approve_clean.` });
610
707
  }
611
708
 
612
709
  case 'get_stats': {
@@ -634,7 +731,7 @@ function text(obj) {
634
731
  // LW-3(a): export the pure helpers so they can be unit-tested without starting
635
732
  // the stdio transport. When this file is required (not run directly), stop here
636
733
  // before the CLI dispatch and MCP startup below.
637
- module.exports = { fenceUnlockResult, UNTRUSTED_CONTENT_ADVISORY, baseHeaders };
734
+ module.exports = { fenceUnlockResult, UNTRUSTED_CONTENT_ADVISORY, baseHeaders, planApproveClean, unlockPaymentRequired, shapeWithdrawStatus, verifyWalletRequestBody };
638
735
  if (require.main !== module) {
639
736
  return;
640
737
  }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "auxilo-mcp",
3
- "version": "0.9.1",
4
- "description": "MCP server for Auxilo \u2014 Agent Capability Discovery & Knowledge Marketplace",
3
+ "version": "0.9.2",
4
+ "mcpName": "io.github.silent-architects/auxilo",
5
+ "description": "MCP server for Auxilo. Your agent stops solving the same problem twice: auto-extracted learnings, free self-unlocks, and earnings when other agents unlock yours.",
5
6
  "main": "mcp-server.js",
6
7
  "bin": {
7
8
  "auxilo-mcp": "mcp-server.js",
@@ -15,6 +16,7 @@
15
16
  "lib/sensitivity-filter.js",
16
17
  "scripts/runner.js",
17
18
  "scripts/capture-core.js",
19
+ "scripts/extract-local.js",
18
20
  "scripts/sources/",
19
21
  "scripts/hooks/auxilo-extract.sh",
20
22
  "README.md",
@@ -26,9 +28,16 @@
26
28
  },
27
29
  "keywords": [
28
30
  "mcp",
31
+ "mcp-server",
29
32
  "model-context-protocol",
33
+ "claude",
34
+ "claude-code",
35
+ "cursor",
36
+ "windsurf",
30
37
  "ai-agents",
38
+ "agent-learning",
31
39
  "agent-discovery",
40
+ "knowledge-sharing",
32
41
  "knowledge-marketplace",
33
42
  "x402",
34
43
  "auxilo"