@xgamesfun/solana 0.24.0 → 0.25.1
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/DISCLAIMER.md +1 -1
- package/NOTICE +4 -4
- package/README.md +100 -52
- package/bin/xsolana.mjs +32 -11
- package/examples/blind-forge.json +2 -4
- package/examples/coin-flip-arena.json +2 -5
- package/examples/duel-arena.json +1 -4
- package/examples/fair-presale.json +1 -3
- package/examples/hero-trials.json +14 -2
- package/examples/ideas.json +788 -788
- package/examples/nft-raffle.json +1 -0
- package/examples/partner-vault.json +19 -3
- package/examples/sacrifice-raffle.json +2 -10
- package/examples/shelter-fair.json +1 -0
- package/examples/stakers-draw.json +4 -7
- package/examples/trade-in-shop.json +2 -3
- package/js/approve/details.mjs +117 -48
- package/js/approve/ed25519.mjs +5 -1
- package/js/approve/live-check.mjs +83 -26
- package/js/approve/page.html +217 -78
- package/js/approve/page.js +43 -15
- package/js/approve/server.mjs +276 -74
- package/js/chain/base58.js +7 -1
- package/js/chain/borsh.js +15 -11
- package/js/chain/bytes.js +11 -8
- package/js/chain/core.js +21 -5
- package/js/chain/curve.js +1 -1
- package/js/chain/lookup.js +118 -36
- package/js/chain/mint.js +6 -2
- package/js/chain/pda.js +2 -2
- package/js/chain/programs.js +6 -6
- package/js/chain/rpc.js +40 -14
- package/js/chain/token.js +13 -2
- package/js/chain/token2022.js +55 -16
- package/js/chain/tx.js +32 -22
- package/js/client/accounts.js +270 -80
- package/js/client/compressed.js +4 -3
- package/js/client/grant.js +165 -40
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +104 -16
- package/js/client/ix.js +2121 -457
- package/js/client/pda.js +20 -18
- package/js/client/policy.js +12 -5
- package/js/client/random.js +2 -2
- package/js/client/sizes.js +220 -65
- package/js/client/traits.js +14 -5
- package/js/connect/connect.mjs +59 -17
- package/js/economy/check.mjs +3884 -690
- package/js/economy/cost.mjs +381 -57
- package/js/economy/examples.mjs +176 -35
- package/js/economy/explain.mjs +468 -110
- package/js/economy/guide.mjs +282 -59
- package/js/economy/legal.mjs +12 -6
- package/js/economy/live.mjs +62 -20
- package/js/economy/model.mjs +320 -97
- package/js/economy/plan.mjs +1022 -203
- package/js/economy/review.mjs +760 -0
- package/js/economy/schema.json +134 -372
- package/js/economy/secrets.mjs +17 -7
- package/js/economy/simulate.mjs +771 -178
- package/js/economy/trust.mjs +15 -0
- package/js/economy/txsize.mjs +15 -3
- package/js/economy/validate.mjs +61 -24
- package/js/index.js +1 -0
- package/js/mcp/server.mjs +43 -15
- package/js/mcp/tools.mjs +318 -80
- package/js/play/index.js +1960 -461
- package/js/play/keys.js +35 -9
- package/js/results/index.js +225 -32
- package/js/results/server.js +230 -0
- package/js/results/wire.js +64 -16
- package/package.json +6 -4
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +52 -22
- package/skills/xsolana-economy/references/after-launch.md +32 -21
- package/skills/xsolana-economy/references/apps.md +47 -36
- package/skills/xsolana-economy/references/sentence-to-spec.md +149 -129
- package/skills/xsolana-economy/references/telling-the-person.md +15 -6
- package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
- package/types/chain/core.d.ts +1 -1
- package/types/chain/lookup.d.ts +1 -1
- package/types/client/accounts.d.ts +24 -3
- package/types/client/grant.d.ts +2 -2
- package/types/client/idl/loot.d.ts +22 -0
- package/types/client/ix.d.ts +54 -45
- package/types/client/pda.d.ts +3 -1
- package/types/economy/cost.d.mts +1 -1
- package/types/economy/explain.d.mts +3 -0
- package/types/economy/guide.d.mts +5 -0
- package/types/economy/model.d.mts +48 -3
- package/types/economy/plan.d.mts +44 -2
- package/types/economy/review.d.mts +45 -0
- package/types/economy/trust.d.mts +8 -0
- package/types/index.d.ts +1 -0
- package/types/mcp/tools.d.mts +62 -0
- package/types/play/index.d.ts +14 -25
- package/types/results/index.d.ts +8 -7
- package/types/results/server.d.ts +126 -0
- package/types/results/wire.d.ts +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// How far a result a voucher's server signs can be trusted: the levels a grant declares in `trust`, and each in
|
|
2
|
+
// words. The spec's own note about its server: never sent on chain. Pure.
|
|
3
|
+
|
|
4
|
+
/** The trust levels a voucher grant declares, strongest first. */
|
|
5
|
+
export const TRUST_LEVELS = Object.freeze(['decides', 'replays', 'bounds']);
|
|
6
|
+
|
|
7
|
+
/** Each trust level in words: what the server does, and what a cheater still gets. */
|
|
8
|
+
export const TRUST_WORDS = Object.freeze({
|
|
9
|
+
decides:
|
|
10
|
+
'the server decides the result itself (it runs the game or the match; the page sends only inputs): nothing the page says can forge one, though a bot that plays well still earns what a good player does',
|
|
11
|
+
replays:
|
|
12
|
+
'the server runs the page’s inputs again and reads the result itself: a forged result fails, though a bot that plays well still earns, and one that knows the run’s randomness up front can search for the best run offline',
|
|
13
|
+
bounds:
|
|
14
|
+
'the page runs the game and reports the result, and the server checks only its pace, order and daily caps: a script that waits as long as a person would gets every reward, up to the limits',
|
|
15
|
+
});
|
package/js/economy/txsize.mjs
CHANGED
|
@@ -18,7 +18,7 @@ export const MAX_TRANSACTION_BYTES = 1232;
|
|
|
18
18
|
export const WALLET_ROOM = 64;
|
|
19
19
|
export const BUDGET = MAX_TRANSACTION_BYTES - WALLET_ROOM;
|
|
20
20
|
|
|
21
|
-
export const compact =
|
|
21
|
+
export const compact = n => (n < 0x80 ? 1 : n < 0x4000 ? 2 : 3);
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* instructions: [{ program: name, accounts: [names], data: bytes }]; signers: [names] (the fee payer first).
|
|
@@ -30,8 +30,20 @@ export function transactionSize({ instructions, signers }) {
|
|
|
30
30
|
for (const a of ix.accounts) keys.add(a);
|
|
31
31
|
keys.add(`program:${ix.program}`);
|
|
32
32
|
}
|
|
33
|
-
const body = instructions.reduce(
|
|
34
|
-
|
|
33
|
+
const body = instructions.reduce(
|
|
34
|
+
(n, ix) => n + 1 + compact(ix.accounts.length) + ix.accounts.length + compact(ix.data) + ix.data,
|
|
35
|
+
0,
|
|
36
|
+
);
|
|
37
|
+
return (
|
|
38
|
+
compact(signers.length) +
|
|
39
|
+
64 * signers.length +
|
|
40
|
+
3 +
|
|
41
|
+
compact(keys.size) +
|
|
42
|
+
32 * keys.size +
|
|
43
|
+
32 +
|
|
44
|
+
compact(instructions.length) +
|
|
45
|
+
body
|
|
46
|
+
);
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
/** How many accounts the transaction locks: each account once, its programs and signers included. */
|
package/js/economy/validate.mjs
CHANGED
|
@@ -5,41 +5,67 @@ import { readFileSync } from 'node:fs';
|
|
|
5
5
|
|
|
6
6
|
export const SCHEMA = JSON.parse(readFileSync(new URL('./schema.json', import.meta.url), 'utf8'));
|
|
7
7
|
|
|
8
|
-
const typeOf =
|
|
8
|
+
const typeOf = v => (v === null ? 'null' : Array.isArray(v) ? 'array' : Number.isInteger(v) ? 'integer' : typeof v);
|
|
9
9
|
const isType = (v, t) => t === typeOf(v) || (t === 'number' && typeof v === 'number' && Number.isFinite(v));
|
|
10
|
-
const show =
|
|
10
|
+
const show = v => JSON.stringify(v)?.slice(0, 60);
|
|
11
11
|
/** "a" or "an", as the word after it takes: "an object", "an array", "a string". */
|
|
12
|
-
const article =
|
|
12
|
+
const article = word => (/^[aeiou]/.test(word) ? `an ${word}` : `a ${word}`);
|
|
13
13
|
/** A value as text with its objects' keys in order, so two equal values read the same (for uniqueItems). */
|
|
14
|
-
const canonical =
|
|
14
|
+
const canonical = v =>
|
|
15
|
+
JSON.stringify(v, (_, x) =>
|
|
16
|
+
x && typeof x === 'object' && !Array.isArray(x)
|
|
17
|
+
? Object.fromEntries(Object.entries(x).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
18
|
+
: x,
|
|
19
|
+
);
|
|
15
20
|
|
|
16
21
|
function resolve(root, ref) {
|
|
17
22
|
if (!ref.startsWith('#/')) throw new Error(`Only local $ref is supported: ${ref}`);
|
|
18
|
-
return ref
|
|
23
|
+
return ref
|
|
24
|
+
.slice(2)
|
|
25
|
+
.split('/')
|
|
26
|
+
.reduce((node, key) => node?.[key], root);
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
/** Every way `value` breaks `schema`, as { path, message }. An empty list means it is valid. */
|
|
22
30
|
export function validate(value, schema = SCHEMA, root = schema, path = 'economy') {
|
|
23
31
|
const errors = [];
|
|
24
|
-
const add =
|
|
32
|
+
const add = message => errors.push({ path, message });
|
|
25
33
|
if (schema.$ref) return validate(value, resolve(root, schema.$ref), root, path);
|
|
26
34
|
if (schema.anyOf) {
|
|
27
|
-
const results = schema.anyOf.map(
|
|
28
|
-
if (!results.some(
|
|
29
|
-
const wanted =
|
|
35
|
+
const results = schema.anyOf.map(s => validate(value, s, root, path));
|
|
36
|
+
if (!results.some(r => r.length === 0)) {
|
|
37
|
+
const wanted =
|
|
38
|
+
schema.description ??
|
|
39
|
+
results
|
|
40
|
+
.flat()
|
|
41
|
+
.map(e => e.message)
|
|
42
|
+
.join('; or ');
|
|
30
43
|
add(`${show(value)} is not allowed here (${wanted})`);
|
|
31
44
|
}
|
|
32
45
|
return errors;
|
|
33
46
|
}
|
|
34
|
-
// Exactly one of the shapes: none is not allowed, and two at once would be read two ways.
|
|
47
|
+
// Exactly one of the shapes: none is not allowed, and two at once would be read two ways. Shapes that only name a
|
|
48
|
+
// field each (`required`) are one choice among fields of an object, whose other keywords still apply.
|
|
35
49
|
if (schema.oneOf) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
const fields = schema.oneOf.every(s => Object.keys(s).length === 1 && s.required?.length === 1)
|
|
51
|
+
? schema.oneOf.map(s => s.required[0])
|
|
52
|
+
: null;
|
|
53
|
+
const results = schema.oneOf.map(s => validate(value, s, root, path));
|
|
54
|
+
const fits = results.filter(r => r.length === 0).length;
|
|
55
|
+
if (fields && typeOf(value) === 'object') {
|
|
56
|
+
if (fits !== 1) add(`needs exactly one of ${fields.map(f => `"${f}"`).join(' or ')}${fits ? ', not both' : ''}`);
|
|
57
|
+
} else {
|
|
58
|
+
if (fits !== 1) {
|
|
59
|
+
const wanted =
|
|
60
|
+
schema.description ??
|
|
61
|
+
results
|
|
62
|
+
.flat()
|
|
63
|
+
.map(e => e.message)
|
|
64
|
+
.join('; or ');
|
|
65
|
+
add(`${show(value)} is not allowed here (${fits ? 'it fits more than one of its forms: ' : ''}${wanted})`);
|
|
66
|
+
}
|
|
67
|
+
return errors;
|
|
41
68
|
}
|
|
42
|
-
return errors;
|
|
43
69
|
}
|
|
44
70
|
if (schema.not && validate(value, schema.not, root, path).length === 0) add(`${show(value)} is not allowed here`);
|
|
45
71
|
if ('const' in schema && value !== schema.const) add(`must be ${show(schema.const)}`);
|
|
@@ -50,13 +76,16 @@ export function validate(value, schema = SCHEMA, root = schema, path = 'economy'
|
|
|
50
76
|
}
|
|
51
77
|
if (typeof value === 'string') {
|
|
52
78
|
if (schema.pattern && !new RegExp(schema.pattern).test(value)) add(`${show(value)} is not in the expected form`);
|
|
53
|
-
if (schema.minLength !== undefined && value.length < schema.minLength)
|
|
54
|
-
|
|
79
|
+
if (schema.minLength !== undefined && value.length < schema.minLength)
|
|
80
|
+
add(`must have at least ${schema.minLength} characters`);
|
|
81
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength)
|
|
82
|
+
add(`must have at most ${schema.maxLength} characters`);
|
|
55
83
|
}
|
|
56
84
|
if (typeof value === 'number') {
|
|
57
85
|
if (schema.minimum !== undefined && value < schema.minimum) add(`must be at least ${schema.minimum}`);
|
|
58
86
|
if (schema.maximum !== undefined && value > schema.maximum) add(`must be at most ${schema.maximum}`);
|
|
59
|
-
if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)
|
|
87
|
+
if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)
|
|
88
|
+
add(`must be more than ${schema.exclusiveMinimum}`);
|
|
60
89
|
}
|
|
61
90
|
if (Array.isArray(value)) {
|
|
62
91
|
if (schema.minItems !== undefined && value.length < schema.minItems) add(`needs at least ${schema.minItems}`);
|
|
@@ -65,7 +94,11 @@ export function validate(value, schema = SCHEMA, root = schema, path = 'economy'
|
|
|
65
94
|
const seen = new Map();
|
|
66
95
|
value.forEach((v, i) => {
|
|
67
96
|
const text = canonical(v);
|
|
68
|
-
if (seen.has(text))
|
|
97
|
+
if (seen.has(text))
|
|
98
|
+
errors.push({
|
|
99
|
+
path: `${path}[${i}]`,
|
|
100
|
+
message: `is the same as ${path}[${seen.get(text)}]: each is listed once`,
|
|
101
|
+
});
|
|
69
102
|
else seen.set(text, i);
|
|
70
103
|
});
|
|
71
104
|
}
|
|
@@ -75,10 +108,14 @@ export function validate(value, schema = SCHEMA, root = schema, path = 'economy'
|
|
|
75
108
|
for (const key of schema.required ?? []) if (!(key in value)) add(`needs "${key}"`);
|
|
76
109
|
for (const [key, v] of Object.entries(value)) {
|
|
77
110
|
const where = `${path}.${key}`;
|
|
78
|
-
if (schema.propertyNames?.pattern && !new RegExp(schema.propertyNames.pattern).test(key))
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
111
|
+
if (schema.propertyNames?.pattern && !new RegExp(schema.propertyNames.pattern).test(key))
|
|
112
|
+
errors.push({ path: where, message: 'is not a name this place takes' });
|
|
113
|
+
if (schema.properties && key in schema.properties)
|
|
114
|
+
errors.push(...validate(v, schema.properties[key], root, where));
|
|
115
|
+
else if (schema.additionalProperties === false)
|
|
116
|
+
errors.push({ path: where, message: 'is not a field this place takes' });
|
|
117
|
+
else if (typeOf(schema.additionalProperties) === 'object')
|
|
118
|
+
errors.push(...validate(v, schema.additionalProperties, root, where));
|
|
82
119
|
}
|
|
83
120
|
}
|
|
84
121
|
return errors;
|
package/js/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { simulate, explain } from './economy/simulate.mjs';
|
|
|
4
4
|
export { plan, setupSteps, playSteps } from './economy/plan.mjs';
|
|
5
5
|
export { cost, explainCost } from './economy/cost.mjs';
|
|
6
6
|
export { explainEconomy } from './economy/explain.mjs';
|
|
7
|
+
export { review, describeReview, SEVERITIES, TRUST_LEVELS, TRUST_WORDS } from './economy/review.mjs';
|
|
7
8
|
export { model } from './economy/model.mjs';
|
|
8
9
|
export { prepare, recordLanded, changedParts } from './economy/live.mjs';
|
|
9
10
|
export { guideText, catalog, CANNOT } from './economy/guide.mjs';
|
package/js/mcp/server.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { PROGRAM_INFO, tools, VERSION } from './tools.mjs';
|
|
|
10
10
|
|
|
11
11
|
export const INSTRUCTIONS = [
|
|
12
12
|
'loot is one shared Solana program for game and community economies (shops, loot boxes, lotteries, auctions, staking farms, crafting, contests, schedules, perks for holders of any NFT, capped rewards). An economy is a JSON spec, not a new smart contract: never write Anchor or Rust for what loot covers.',
|
|
13
|
-
'Start with xsolana_guide, then xsolana_examples with the person’s own words; write the spec; xsolana_check after every change; xsolana_simulate and xsolana_explain before telling them what it does; xsolana_save; xsolana_publish when they say to put it live (they approve in their own wallet: you never sign or ask for a key).',
|
|
13
|
+
'Start with xsolana_guide, then xsolana_examples with the person’s own words; write the spec; xsolana_check after every change; xsolana_simulate and xsolana_explain before telling them what it does; xsolana_review before going live (fix its high findings, say honestly what each voucher server checks, and tell the person what a cheater gets); xsolana_save; xsolana_publish when they say to put it live (they approve in their own wallet: you never sign or ask for a key).',
|
|
14
14
|
`Program ${PROGRAM_INFO.program} on ${PROGRAM_INFO.cluster} (loot ${VERSION}). Devnet only until audited.`,
|
|
15
15
|
].join(' ');
|
|
16
16
|
|
|
@@ -23,15 +23,18 @@ export const PROTOCOLS = ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'
|
|
|
23
23
|
/** Every way `input` does not fit a tool's input schema, in words; [] when it fits. */
|
|
24
24
|
export function inputProblems(schema, input) {
|
|
25
25
|
if (!input || typeof input !== 'object' || Array.isArray(input)) return ['arguments must be an object'];
|
|
26
|
-
return validate(input, schema, schema, 'arguments').map(
|
|
26
|
+
return validate(input, schema, schema, 'arguments').map(e => `${e.path} ${e.message}`);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** Runs one tool by name: the same checks and answer on the server and in the terminal. */
|
|
30
30
|
export async function runTool(name, input = {}, ctx = {}) {
|
|
31
|
-
const tool = tools.find(
|
|
31
|
+
const tool = tools.find(t => t.name === name);
|
|
32
32
|
if (!tool) {
|
|
33
|
-
const near = tools.filter(
|
|
34
|
-
return {
|
|
33
|
+
const near = tools.filter(t => t.name.includes(String(name).replace(/^xsolana_/, ''))).map(t => t.name);
|
|
34
|
+
return {
|
|
35
|
+
isError: true,
|
|
36
|
+
text: `No tool ${name}.${near.length ? ` Did you mean ${near.join(', ')}?` : ''} Tools: ${tools.map(t => t.name).join(', ')}.`,
|
|
37
|
+
};
|
|
35
38
|
}
|
|
36
39
|
const bad = inputProblems(tool.inputSchema, input);
|
|
37
40
|
if (bad.length) return { isError: true, text: bad.join('; ') };
|
|
@@ -42,27 +45,48 @@ export async function runTool(name, input = {}, ctx = {}) {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
const jsonable =
|
|
48
|
+
const jsonable = value =>
|
|
49
|
+
JSON.parse(
|
|
50
|
+
JSON.stringify(value, (_, v) =>
|
|
51
|
+
typeof v === 'bigint' ? v.toString() : v instanceof Uint8Array ? Buffer.from(v).toString('hex') : v,
|
|
52
|
+
),
|
|
53
|
+
);
|
|
46
54
|
|
|
47
55
|
/** The answer to one JSON-RPC request, or null for a notification. */
|
|
48
56
|
export async function handle(message, ctx = {}) {
|
|
49
57
|
const { id, method, params } = message ?? {};
|
|
50
|
-
const reply =
|
|
58
|
+
const reply = result => (id === undefined ? null : { jsonrpc: '2.0', id, result });
|
|
51
59
|
const fail = (code, text) => (id === undefined ? null : { jsonrpc: '2.0', id, error: { code, message: text } });
|
|
52
60
|
switch (method) {
|
|
53
61
|
case 'initialize': {
|
|
54
62
|
const asked = params?.protocolVersion;
|
|
55
|
-
return reply({
|
|
63
|
+
return reply({
|
|
64
|
+
protocolVersion: PROTOCOLS.includes(asked) ? asked : PROTOCOLS[0],
|
|
65
|
+
capabilities: { tools: {} },
|
|
66
|
+
serverInfo: { name: 'xsolana', version: VERSION },
|
|
67
|
+
instructions: INSTRUCTIONS,
|
|
68
|
+
});
|
|
56
69
|
}
|
|
57
70
|
case 'ping':
|
|
58
71
|
return reply({});
|
|
59
72
|
case 'tools/list':
|
|
60
|
-
return reply({
|
|
73
|
+
return reply({
|
|
74
|
+
tools: tools.map(t => ({
|
|
75
|
+
name: t.name,
|
|
76
|
+
description: t.description,
|
|
77
|
+
inputSchema: t.inputSchema,
|
|
78
|
+
annotations: t.annotations,
|
|
79
|
+
})),
|
|
80
|
+
});
|
|
61
81
|
case 'tools/call': {
|
|
62
82
|
const result = await runTool(params?.name, params?.arguments ?? {}, { ...ctx, cwd: ctx.cwd ?? process.cwd() });
|
|
63
83
|
// A tool that failed says so (isError), and keeps whatever it found (a save's refusals, say) as structuredContent.
|
|
64
84
|
const { text, isError, ...data } = jsonable(result);
|
|
65
|
-
return reply({
|
|
85
|
+
return reply({
|
|
86
|
+
...(isError ? { isError: true } : {}),
|
|
87
|
+
content: [{ type: 'text', text }],
|
|
88
|
+
structuredContent: { text, ...data },
|
|
89
|
+
});
|
|
66
90
|
}
|
|
67
91
|
default:
|
|
68
92
|
if (method?.startsWith('notifications/')) return null;
|
|
@@ -74,15 +98,19 @@ export async function handle(message, ctx = {}) {
|
|
|
74
98
|
* Serves the tools over stdio until stdin closes. Once it closes, every request already read is answered before
|
|
75
99
|
* `exit` runs, so a client that sends its last request and closes still gets the reply.
|
|
76
100
|
*/
|
|
77
|
-
export async function serveStdio({
|
|
101
|
+
export async function serveStdio({
|
|
102
|
+
input = process.stdin,
|
|
103
|
+
output = process.stdout,
|
|
104
|
+
exit = () => process.exit(0),
|
|
105
|
+
} = {}) {
|
|
78
106
|
console.log = (...a) => console.error(...a);
|
|
79
107
|
console.info = (...a) => console.error(...a);
|
|
80
108
|
let buffer = '';
|
|
81
|
-
const write =
|
|
109
|
+
const write = m => m && output.write(`${JSON.stringify(m)}\n`);
|
|
82
110
|
// The requests read and not yet answered.
|
|
83
111
|
const pending = new Set();
|
|
84
112
|
input.setEncoding('utf8');
|
|
85
|
-
input.on('data',
|
|
113
|
+
input.on('data', chunk => {
|
|
86
114
|
buffer += chunk;
|
|
87
115
|
let at;
|
|
88
116
|
while ((at = buffer.indexOf('\n')) >= 0) {
|
|
@@ -98,7 +126,7 @@ export async function serveStdio({ input = process.stdin, output = process.stdou
|
|
|
98
126
|
}
|
|
99
127
|
for (const m of Array.isArray(message) ? message : [message]) {
|
|
100
128
|
const done = handle(m)
|
|
101
|
-
.then(write,
|
|
129
|
+
.then(write, e => write({ jsonrpc: '2.0', id: m?.id ?? null, error: { code: -32603, message: e.message } }))
|
|
102
130
|
.finally(() => pending.delete(done));
|
|
103
131
|
pending.add(done);
|
|
104
132
|
}
|
|
@@ -109,5 +137,5 @@ export async function serveStdio({ input = process.stdin, output = process.stdou
|
|
|
109
137
|
// (the last reply is written out before the process goes)
|
|
110
138
|
output.write('', () => exit());
|
|
111
139
|
});
|
|
112
|
-
console.error(`xsolana MCP server ${VERSION}: ${tools.length} tools (${tools.map(
|
|
140
|
+
console.error(`xsolana MCP server ${VERSION}: ${tools.length} tools (${tools.map(t => t.name).join(', ')})`);
|
|
113
141
|
}
|