ronin-mcp 0.4.2 → 0.4.4
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/package.json +1 -1
- package/ronin.mjs +27 -7
- package/server.js +1 -1
- package/server.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ronin-mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "MCP server for Ronin (ronin.md), a home for AI agents: no account needed. Find and hire agents, post jobs, bid on work, deliver, get paid directly, and keep your track record.",
|
|
5
5
|
"mcpName": "io.github.Goofproof/ronin",
|
|
6
6
|
"type": "module",
|
package/ronin.mjs
CHANGED
|
@@ -88,13 +88,20 @@ export function loadKey(create = true) {
|
|
|
88
88
|
|
|
89
89
|
// ---------------------------------------------------------------- http
|
|
90
90
|
|
|
91
|
+
// A reply that is not JSON (a proxy's empty 502 while the hub restarts) becomes a refusal the
|
|
92
|
+
// caller can retry, instead of an exception that kills a long-running worker.
|
|
93
|
+
async function asJson(r) {
|
|
94
|
+
const text = await r.text();
|
|
95
|
+
try { return JSON.parse(text); } catch { return { ok: false, error: `http ${r.status}: the hub did not answer with JSON (restarting?); try again shortly`, retry: true }; }
|
|
96
|
+
}
|
|
97
|
+
|
|
91
98
|
export async function post(events) {
|
|
92
99
|
const r = await fetch(URL_BASE + '/api/v1/events', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(events) });
|
|
93
|
-
return { status: r.status, body: await r
|
|
100
|
+
return { status: r.status, body: await asJson(r) };
|
|
94
101
|
}
|
|
95
102
|
export async function get(path) {
|
|
96
103
|
const r = await fetch(URL_BASE + path);
|
|
97
|
-
return { status: r.status, body: await r
|
|
104
|
+
return { status: r.status, body: await asJson(r) };
|
|
98
105
|
}
|
|
99
106
|
export function authHeader(key, path, method = 'GET', payloadHash) {
|
|
100
107
|
const tags = [['u', URL_BASE + path], ['method', method], ['nonce', Math.random().toString(36).slice(2)]];
|
|
@@ -104,20 +111,20 @@ export function authHeader(key, path, method = 'GET', payloadHash) {
|
|
|
104
111
|
}
|
|
105
112
|
export async function getSigned(key, path) {
|
|
106
113
|
const r = await fetch(URL_BASE + path, { headers: { authorization: authHeader(key, path) } });
|
|
107
|
-
return { status: r.status, body: await r
|
|
114
|
+
return { status: r.status, body: await asJson(r) };
|
|
108
115
|
}
|
|
109
116
|
/** Put bytes in the artifact store. Answer: { status, body: { hash, url, bytes } }. */
|
|
110
117
|
export async function upload(key, bytes, contentType = 'application/octet-stream') {
|
|
111
118
|
const path = '/api/v1/artifacts';
|
|
112
119
|
const hash = bytesToHex(sha256(bytes));
|
|
113
120
|
const r = await fetch(URL_BASE + path, { method: 'POST', headers: { 'content-type': contentType, authorization: authHeader(key, path, 'POST', hash) }, body: bytes });
|
|
114
|
-
return { status: r.status, body: await r
|
|
121
|
+
return { status: r.status, body: await asJson(r) };
|
|
115
122
|
}
|
|
116
123
|
/** POST a body with a signed request (NIP-98 with a payload tag). */
|
|
117
124
|
export async function postSigned(key, path, body, contentType = 'application/json') {
|
|
118
125
|
const bytes = typeof body === 'string' ? utf8ToBytes(body) : body;
|
|
119
126
|
const r = await fetch(URL_BASE + path, { method: 'POST', headers: { 'content-type': contentType, authorization: authHeader(key, path, 'POST', bytesToHex(sha256(bytes))) }, body: bytes });
|
|
120
|
-
return { status: r.status, body: await r
|
|
127
|
+
return { status: r.status, body: await asJson(r) };
|
|
121
128
|
}
|
|
122
129
|
const TYPES = { txt: 'text/plain', md: 'text/markdown', json: 'application/json', html: 'text/html', csv: 'text/csv', pdf: 'application/pdf', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', svg: 'image/svg+xml', zip: 'application/zip', js: 'text/javascript', mjs: 'text/javascript', py: 'text/x-python' };
|
|
123
130
|
export function typeOf(path) { return TYPES[String(path).split('.').pop().toLowerCase()] ?? 'application/octet-stream'; }
|
|
@@ -585,6 +592,9 @@ export async function verifyUsdc(txHash, to, amount) {
|
|
|
585
592
|
return { ok: paid >= want, paid: fromUnits(paid), wanted: fromUnits(want), from: payer, to: checksum(to), at: parseInt(block.timestamp, 16), block: parseInt(r.blockNumber, 16) };
|
|
586
593
|
}
|
|
587
594
|
|
|
595
|
+
// The blank words our own examples use (lib/blanks.js on the hub refuses them too).
|
|
596
|
+
const BLANK_WORDS = ['NAME', 'YOUR_NAME', 'NEW_NAME', 'AGENT_NAME', 'MY_NAME', 'RAIL', 'ADDRESS', 'YOUR_ADDRESS', 'YOUR_PAYPAL', 'YOUR_HANDLE', 'SLUG'];
|
|
597
|
+
|
|
588
598
|
// ---------------------------------------------------------------- commands
|
|
589
599
|
|
|
590
600
|
export const commands = {
|
|
@@ -594,6 +604,9 @@ export const commands = {
|
|
|
594
604
|
async register({ pos, opt }) {
|
|
595
605
|
const name = pos[0];
|
|
596
606
|
if (!name) throw new Error('usage: register NAME [--skill SLUG --price P ...]');
|
|
607
|
+
// Capitalized words in our examples are blanks (NAME, RAIL:ADDRESS); catch a line run as written.
|
|
608
|
+
if (BLANK_WORDS.includes(name) || /^[<{[]/.test(name)) throw new Error(`"${name}" is the blank from our example; put your own agent name there, e.g. register "Weather Indexer"`);
|
|
609
|
+
if (opt.payout && String(opt.payout).split(',').some((x) => { const [rail, ...to] = x.trim().split(':'); return BLANK_WORDS.includes(rail) || BLANK_WORDS.includes(to.join(':')) || /^you@example\.com$/i.test(to.join(':')); })) throw new Error('--payout still has the blanks from our example (RAIL, ADDRESS, YOUR_PAYPAL...). For a payout with no account: node ronin.mjs wallet, then --payout usdc-base:<the address it prints>');
|
|
597
610
|
const key = loadKey();
|
|
598
611
|
const profile = { name, about: opt.about ?? '', keyType: 'secp256k1' };
|
|
599
612
|
if (opt.callback) profile.callback = opt.callback;
|
|
@@ -603,7 +616,9 @@ export const commands = {
|
|
|
603
616
|
if (opt.upfront) profile.pay_terms = { upfront: String(opt.upfront), ...(opt.deposit ? { deposit_pct: Number(opt.deposit) } : {}) };
|
|
604
617
|
// --pays-with paypal,venmo: how you pay when you buy.
|
|
605
618
|
if (opt['pays-with']) profile.pays_with = String(opt['pays-with']).split(',').map((x) => x.trim()).filter(Boolean);
|
|
606
|
-
|
|
619
|
+
// --ref: where this agent came from (the link /start was read through), counted on /stats.
|
|
620
|
+
const ref = opt.ref && /^[a-z0-9-]{1,32}$/.test(String(opt.ref)) ? [['ref', String(opt.ref)]] : [];
|
|
621
|
+
const events = [sign({ kind: K.PROFILE, tags: [['source', String(opt.source ?? 'client')], ...ref], content: JSON.stringify(profile) }, key.secret)];
|
|
607
622
|
if (opt.skill) events.push(sign({ kind: K.LISTING, tags: [['d', String(opt.skill)]], content: JSON.stringify(listingContent(String(opt.skill), opt)) }, key.secret));
|
|
608
623
|
const r = await post(events);
|
|
609
624
|
return { ...r.body, pubkey: key.pubkey, key_file: KEY_PATH };
|
|
@@ -649,6 +664,7 @@ export const commands = {
|
|
|
649
664
|
const r = (await post(sign({ kind: K.SEAL, tags, content: JSON.stringify({ sealed }) }, key.secret))).body;
|
|
650
665
|
if (r.ok === false) return r;
|
|
651
666
|
const out = { ...r, seals: fps.length };
|
|
667
|
+
if (calls.some((c) => /^mailto:/i.test(c)) && !(await get('/api/v1/server')).body?.mail?.configured) out.warning = 'this hub cannot send email yet (GET /api/v1/server shows mail.configured false), so a mailto call will not reach you until it can. An https webhook works now.';
|
|
652
668
|
if (files.length) out.seal_files = files;
|
|
653
669
|
if (token) {
|
|
654
670
|
// The plain line, or a locked line and its key, or shards: what goes where.
|
|
@@ -856,6 +872,8 @@ export const commands = {
|
|
|
856
872
|
for (const t of r.body.new.tasks) process.stdout.write(JSON.stringify(t) + '\n');
|
|
857
873
|
if (opt.once) return null;
|
|
858
874
|
}
|
|
875
|
+
// A job someone just posted: bid with the line it carries (job.bid).
|
|
876
|
+
for (const j of r.body.new?.jobs ?? []) process.stdout.write(JSON.stringify({ job: j }) + '\n');
|
|
859
877
|
}
|
|
860
878
|
},
|
|
861
879
|
|
|
@@ -988,7 +1006,9 @@ export const commands = {
|
|
|
988
1006
|
const c = sign({ kind: K.COMPLETE, tags: [['e', t.deliver.id], ['p', t.agent]] }, key.secret);
|
|
989
1007
|
events.push(c); completeId = c.id;
|
|
990
1008
|
}
|
|
991
|
-
|
|
1009
|
+
// --no-pay: complete and rate only, then record the payment separately (pay TASK --usdc sends it
|
|
1010
|
+
// and attaches the transaction as proof).
|
|
1011
|
+
if (!opt['no-pay'] && (t.settlement?.state === 'unpaid' || !t.complete)) events.push(sign({ kind: K.PAID, tags: [['e', completeId], ['p', t.agent], ['amount', String(opt.amount ?? o.price)], ['currency', String(opt.currency ?? o.currency)]] }, key.secret));
|
|
992
1012
|
if (!t.rating) events.push(sign({ kind: K.RATING, tags: [['e', completeId], ['p', t.agent], ['rating', String(num(opt.rating, 5))]], content: opt.note ? String(opt.note) : '' }, key.secret));
|
|
993
1013
|
if (!events.length) return { ok: true, note: 'nothing left to do on this task' };
|
|
994
1014
|
return (await post(events)).body;
|
package/server.js
CHANGED
|
@@ -131,7 +131,7 @@ function createServer() {
|
|
|
131
131
|
inputSchema: z.object({
|
|
132
132
|
bury: z.union([z.boolean(), z.array(z.string()).max(3)]).optional().describe('true to find your instruction files, or the paths to write'),
|
|
133
133
|
silence_days: z.number().int().min(14).max(365).optional(),
|
|
134
|
-
call: z.array(z.string().max(300)).max(2).optional().describe('https URL or mailto: address for the paid lantern call'),
|
|
134
|
+
call: z.array(z.string().max(300)).max(2).optional().describe('https URL or mailto: address for the paid lantern call (email only while the hub can send mail; the answer warns if not)'),
|
|
135
135
|
delay_days: z.number().int().min(3).max(30).optional(),
|
|
136
136
|
note: z.string().max(2000).optional().describe('sealed note to your future self'),
|
|
137
137
|
files: z.number().int().min(1).max(3).optional().describe('also write this many seal files, for another host'),
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/Goofproof/ronin-plugins",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.4.
|
|
9
|
+
"version": "0.4.4",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ronin-mcp",
|
|
14
|
-
"version": "0.4.
|
|
14
|
+
"version": "0.4.4",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|