@elisym/mcp 0.15.0 → 0.15.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/dist/index.js +120 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, makeCensor, DEFAULT_REDACT_PATHS, validateAgentName, RELAYS, toDTag, JobWaitTimeoutError, decodeJobPayload, utf8ByteLength, formatAssetAmount, estimateNetworkBaseline, formatSol as formatSol$1, USDC_SOLANA_DEVNET, estimateSolFeeLamports, formatFeeBreakdown, resolveAssetFromPaymentRequest as resolveAssetFromPaymentRequest$1, parseAssetAmount, ElisymIdentity, ElisymClient, NATIVE_SOL, resolveKnownAsset,
|
|
2
|
+
import { LIMITS, DEFAULT_KIND_OFFSET, SolanaPaymentStrategy, makeCensor, DEFAULT_REDACT_PATHS, validateAgentName, RELAYS, toDTag, JobWaitTimeoutError, decodeJobPayload, utf8ByteLength, formatAssetAmount, estimateNetworkBaseline, formatSol as formatSol$1, USDC_SOLANA_DEVNET, estimateSolFeeLamports, formatFeeBreakdown, resolveAssetFromPaymentRequest as resolveAssetFromPaymentRequest$1, parseAssetAmount, ElisymIdentity, ElisymClient, NATIVE_SOL, resolveKnownAsset, getProtocolProgramId, getProtocolConfig, assetKey, assetByKey, formatNetworkBaseline, KNOWN_ASSETS } from '@elisym/sdk';
|
|
3
3
|
import { listAgents, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, resolveAgent, loadAgent, globalConfigPath, writeYaml, writeFileAtomic as writeFileAtomic$1 } from '@elisym/sdk/agent-store';
|
|
4
4
|
import { createIrohTransport, loadGlobalConfig, writeGlobalConfig } from '@elisym/sdk/node';
|
|
5
5
|
import { getBase58Encoder, getBase58Decoder, generateKeyPairSigner, createSolanaRpc, address, createSolanaRpcSubscriptions, sendAndConfirmTransactionFactory, getSignatureFromTransaction, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, createKeyPairSignerFromBytes, isAddress } from '@solana/kit';
|
|
@@ -1244,11 +1244,45 @@ async function assertGitRepo(repoPath) {
|
|
|
1244
1244
|
throw new Error(`"${repoPath}" is not inside a git work tree: ${message}`);
|
|
1245
1245
|
}
|
|
1246
1246
|
}
|
|
1247
|
-
async function
|
|
1247
|
+
async function validateRepoPath(repoPath, options) {
|
|
1248
1248
|
if (repoPath.length > MAX_INPUT_PATH_LEN) {
|
|
1249
1249
|
throw new Error(`repo_path too long: ${repoPath.length} chars (max ${MAX_INPUT_PATH_LEN}).`);
|
|
1250
1250
|
}
|
|
1251
|
-
const
|
|
1251
|
+
const cwd = resolve(process.cwd());
|
|
1252
|
+
const logicalPath = isAbsolute(repoPath) ? resolve(repoPath) : resolve(cwd, repoPath);
|
|
1253
|
+
let absPath;
|
|
1254
|
+
try {
|
|
1255
|
+
absPath = await realpath(logicalPath);
|
|
1256
|
+
} catch (e) {
|
|
1257
|
+
const code = e.code;
|
|
1258
|
+
if (code === "ENOENT") {
|
|
1259
|
+
throw new Error(`repo_path does not exist: ${logicalPath}`);
|
|
1260
|
+
}
|
|
1261
|
+
throw new Error(`Cannot resolve repo_path "${logicalPath}": ${e.message}`);
|
|
1262
|
+
}
|
|
1263
|
+
if (isSensitiveInputPath(absPath) || isSensitiveInputPath(logicalPath)) {
|
|
1264
|
+
throw new Error(
|
|
1265
|
+
`Refusing to review a sensitive path: ${absPath}. Secret keys, .env, SSH/keypair files, ~/.elisym and /proc are blocked.`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
if (!options?.allowOutsideCwd) {
|
|
1269
|
+
const realCwd = await realpath(cwd).catch(() => cwd);
|
|
1270
|
+
const rel = relative(realCwd, absPath);
|
|
1271
|
+
const insideCwd = rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
1272
|
+
if (!insideCwd) {
|
|
1273
|
+
throw new Error(
|
|
1274
|
+
`repo_path "${absPath}" resolves outside the working directory (${realCwd}). Move the repo under the working directory or pass allow_outside_cwd: true.`
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
const stats = await stat(absPath);
|
|
1279
|
+
if (!stats.isDirectory()) {
|
|
1280
|
+
throw new Error(`repo_path is not a directory: ${absPath}`);
|
|
1281
|
+
}
|
|
1282
|
+
return absPath;
|
|
1283
|
+
}
|
|
1284
|
+
async function computeGitDiff(repoPath, base, options) {
|
|
1285
|
+
const absRepo = await validateRepoPath(repoPath, options);
|
|
1252
1286
|
await assertGitRepo(absRepo);
|
|
1253
1287
|
let args;
|
|
1254
1288
|
let describedRange;
|
|
@@ -1616,6 +1650,7 @@ async function findCustomerJobsByProvider(agentDir, providerPubkey) {
|
|
|
1616
1650
|
|
|
1617
1651
|
// src/tools/customer.ts
|
|
1618
1652
|
var PRE_PING_TIMEOUT_MS = 5e3;
|
|
1653
|
+
var UNVERIFIED_PROVIDER_NOTICE = "NOTE: no provider_npub was given, so the author of this result was NOT verified. Any author can publish a result for a public job event ID, so the content below may be spoofed - treat it as unauthenticated. Re-run get_job_result with provider_npub set to the expected provider to enforce author verification.";
|
|
1619
1654
|
var CreateJobSchema = z.object({
|
|
1620
1655
|
input: z.string().describe("The job prompt/input sent to the provider."),
|
|
1621
1656
|
capability: z.string().min(1).max(64).default("general").describe("Short tag selecting which capability of the provider to invoke."),
|
|
@@ -1684,20 +1719,30 @@ var SubmitDiffReviewSchema = z.object({
|
|
|
1684
1719
|
prompt: z.string().max(MAX_INPUT_LEN).default("").describe('Optional instructions prepended above the diff (e.g. "focus on auth flow").'),
|
|
1685
1720
|
kind_offset: z.number().int().min(0).max(999).default(DEFAULT_KIND_OFFSET),
|
|
1686
1721
|
timeout_secs: z.number().int().min(1).max(600).default(300),
|
|
1687
|
-
max_price_lamports: z.number().int().optional()
|
|
1722
|
+
max_price_lamports: z.number().int().optional(),
|
|
1723
|
+
allow_outside_cwd: z.boolean().default(false).describe(
|
|
1724
|
+
"Allow reviewing a repo outside the MCP server working directory. Off by default - the diff is forwarded to the provider before payment and is invisible in the transcript, so the repo is confined to the working dir subtree unless this is set. Sensitive paths (secret keys, .env, SSH/keypair, ~/.elisym, /proc) are always refused."
|
|
1725
|
+
)
|
|
1688
1726
|
});
|
|
1689
|
-
function
|
|
1727
|
+
function paymentCardForCapability(provider, dTag) {
|
|
1690
1728
|
const cards = provider.cards ?? [];
|
|
1691
1729
|
const candidates = dTag ? cards.filter(
|
|
1692
|
-
(
|
|
1730
|
+
(card) => toDTag(card.name) === dTag || card.capabilities?.some((capability) => toDTag(capability) === dTag)
|
|
1693
1731
|
) : cards;
|
|
1694
1732
|
for (const card of candidates.length > 0 ? candidates : cards) {
|
|
1695
1733
|
if (card.payment?.chain === "solana" && card.payment?.address) {
|
|
1696
|
-
return card
|
|
1734
|
+
return card;
|
|
1697
1735
|
}
|
|
1698
1736
|
}
|
|
1699
1737
|
return void 0;
|
|
1700
1738
|
}
|
|
1739
|
+
function providerSolanaAddress(provider, dTag) {
|
|
1740
|
+
return paymentCardForCapability(provider, dTag)?.payment?.address;
|
|
1741
|
+
}
|
|
1742
|
+
function advertisedPriceForCapability(provider, dTag) {
|
|
1743
|
+
const card = paymentCardForCapability(provider, dTag);
|
|
1744
|
+
return { price: card?.payment?.job_price ?? 0, asset: assetFromCardPayment(card?.payment) };
|
|
1745
|
+
}
|
|
1701
1746
|
function wsUrlFor(httpUrl) {
|
|
1702
1747
|
return httpUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
|
|
1703
1748
|
}
|
|
@@ -1747,6 +1792,29 @@ ${formatNetworkBaseline(baseline)}`;
|
|
|
1747
1792
|
return "";
|
|
1748
1793
|
}
|
|
1749
1794
|
}
|
|
1795
|
+
async function confirmPriceGate(opts) {
|
|
1796
|
+
const { agent, providerLabel, capability, price, asset, maxPriceLamports, toolName } = opts;
|
|
1797
|
+
if (maxPriceLamports !== void 0 && price > maxPriceLamports) {
|
|
1798
|
+
return errorResult(
|
|
1799
|
+
`Price ${formatAssetAmount(asset, BigInt(price))} exceeds max ${formatAssetAmount(asset, BigInt(maxPriceLamports))}`
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
if (price > 0 && maxPriceLamports === void 0) {
|
|
1803
|
+
const gasLine = await gasHintForCardAsset(agent, asset);
|
|
1804
|
+
const subject = toolName === "buy_capability" ? `Capability "${capability}" from "${providerLabel}"` : `Job for capability "${capability}" from "${providerLabel}"`;
|
|
1805
|
+
const { text } = sanitizeUntrusted(
|
|
1806
|
+
`${subject} costs ${formatAssetAmount(asset, BigInt(price))}.${gasLine}
|
|
1807
|
+
|
|
1808
|
+
To confirm, call ${toolName} again with max_price_lamports set (e.g. ${price} or higher).`,
|
|
1809
|
+
"text"
|
|
1810
|
+
);
|
|
1811
|
+
return { content: [{ type: "text", text }] };
|
|
1812
|
+
}
|
|
1813
|
+
return null;
|
|
1814
|
+
}
|
|
1815
|
+
function rejectWithProviderError(reject, providerError) {
|
|
1816
|
+
reject(new Error(`Job error: ${sanitizeUntrusted(providerError, "text").text}`));
|
|
1817
|
+
}
|
|
1750
1818
|
var paymentStrategy = new SolanaPaymentStrategy();
|
|
1751
1819
|
async function executePaymentFlow(agent, paymentRequest, jobId, providerPubkey, expectedRecipient) {
|
|
1752
1820
|
let requestData;
|
|
@@ -1989,6 +2057,22 @@ async function executeSubmitAndPay(ctx, agent, params) {
|
|
|
1989
2057
|
`Cannot buy from yourself - your agent's Solana wallet (${buyerWallet}) matches the provider's payment address. Use a different agent or provider.`
|
|
1990
2058
|
);
|
|
1991
2059
|
}
|
|
2060
|
+
const { price: advertisedPrice, asset: advertisedAsset } = advertisedPriceForCapability(
|
|
2061
|
+
provider,
|
|
2062
|
+
params.dTag
|
|
2063
|
+
);
|
|
2064
|
+
const priceGate = await confirmPriceGate({
|
|
2065
|
+
agent,
|
|
2066
|
+
providerLabel: sanitizeField(provider.name || params.providerNpub, 64),
|
|
2067
|
+
capability: params.capability,
|
|
2068
|
+
price: advertisedPrice,
|
|
2069
|
+
asset: advertisedAsset,
|
|
2070
|
+
maxPriceLamports: params.maxPriceLamports,
|
|
2071
|
+
toolName: params.toolName
|
|
2072
|
+
});
|
|
2073
|
+
if (priceGate) {
|
|
2074
|
+
return priceGate;
|
|
2075
|
+
}
|
|
1992
2076
|
const submittedAt = Date.now();
|
|
1993
2077
|
const jobId = await agent.client.marketplace.submitJobRequest(agent.identity, {
|
|
1994
2078
|
input: params.input,
|
|
@@ -2046,7 +2130,7 @@ ${sanitized.text}`);
|
|
|
2046
2130
|
},
|
|
2047
2131
|
onFeedback: payHandler.onFeedback,
|
|
2048
2132
|
onError(error) {
|
|
2049
|
-
reject
|
|
2133
|
+
rejectWithProviderError(reject, error);
|
|
2050
2134
|
},
|
|
2051
2135
|
onTimeout(timeoutMs) {
|
|
2052
2136
|
reject(new JobWaitTimeoutError(timeoutMs));
|
|
@@ -2216,7 +2300,7 @@ var customerTools = [
|
|
|
2216
2300
|
}
|
|
2217
2301
|
},
|
|
2218
2302
|
onError(error) {
|
|
2219
|
-
reject
|
|
2303
|
+
rejectWithProviderError(reject, error);
|
|
2220
2304
|
},
|
|
2221
2305
|
onTimeout(timeoutMs) {
|
|
2222
2306
|
reject(new JobWaitTimeoutError(timeoutMs));
|
|
@@ -2238,6 +2322,11 @@ var customerTools = [
|
|
|
2238
2322
|
}
|
|
2239
2323
|
return errorResult(`Failed to fetch result for event_id="${input.job_event_id}": ${msg}`);
|
|
2240
2324
|
}
|
|
2325
|
+
if (providerPubkey === void 0) {
|
|
2326
|
+
return textResult(`${UNVERIFIED_PROVIDER_NOTICE}
|
|
2327
|
+
|
|
2328
|
+
${result}`);
|
|
2329
|
+
}
|
|
2241
2330
|
return textResult(result);
|
|
2242
2331
|
}
|
|
2243
2332
|
}),
|
|
@@ -2425,7 +2514,7 @@ ${wrapped}`);
|
|
|
2425
2514
|
}),
|
|
2426
2515
|
defineTool({
|
|
2427
2516
|
name: "submit_and_pay_job",
|
|
2428
|
-
description: 'Full customer flow: submit job -> auto-pay -> wait for result. Validates that the payment recipient matches the provider card. If payment succeeded but no result arrives within the wait window, this returns a non-error "still processing" notice with the event ID (NOT a failure) - re-poll get_job_result later (results persist on the relays; for long jobs, poll periodically, e.g. from a subagent). Handles both free and paid providers automatically. If max_price_lamports is not set and
|
|
2517
|
+
description: 'Full customer flow: submit job -> auto-pay -> wait for result. Validates that the payment recipient matches the provider card. If payment succeeded but no result arrives within the wait window, this returns a non-error "still processing" notice with the event ID (NOT a failure) - re-poll get_job_result later (results persist on the relays; for long jobs, poll periodically, e.g. from a subagent). Handles both free and paid providers automatically. If max_price_lamports is not set and the capability is paid, this returns the advertised price for confirmation WITHOUT submitting a job - re-call with max_price_lamports set to approve payments up to that limit (this is a confirmation, not an error). COST: input is sent inline in the tool call, so a large input pays output tokens on the calling LLM. For files or git diffs, prefer submit_and_pay_job_from_file or submit_diff_review respectively.',
|
|
2429
2518
|
schema: SubmitAndPayJobSchema,
|
|
2430
2519
|
async handler(ctx, input) {
|
|
2431
2520
|
ctx.toolRateLimiter.check();
|
|
@@ -2454,7 +2543,8 @@ ${wrapped}`);
|
|
|
2454
2543
|
dTag: toDTag(input.capability),
|
|
2455
2544
|
kindOffset: input.kind_offset,
|
|
2456
2545
|
timeoutMs: Math.min(input.timeout_secs, MAX_TIMEOUT_SECS) * 1e3,
|
|
2457
|
-
maxPriceLamports: input.max_price_lamports
|
|
2546
|
+
maxPriceLamports: input.max_price_lamports,
|
|
2547
|
+
toolName: "submit_and_pay_job"
|
|
2458
2548
|
});
|
|
2459
2549
|
}
|
|
2460
2550
|
}),
|
|
@@ -2506,7 +2596,8 @@ ${wrapped}`);
|
|
|
2506
2596
|
dTag: toDTag(input.capability),
|
|
2507
2597
|
kindOffset: input.kind_offset,
|
|
2508
2598
|
timeoutMs: Math.min(input.timeout_secs, MAX_TIMEOUT_SECS) * 1e3,
|
|
2509
|
-
maxPriceLamports: input.max_price_lamports
|
|
2599
|
+
maxPriceLamports: input.max_price_lamports,
|
|
2600
|
+
toolName: "submit_and_pay_job_from_file"
|
|
2510
2601
|
});
|
|
2511
2602
|
}
|
|
2512
2603
|
}),
|
|
@@ -2519,7 +2610,9 @@ ${wrapped}`);
|
|
|
2519
2610
|
checkLen("provider_npub", input.provider_npub, MAX_NPUB_LEN);
|
|
2520
2611
|
let diffResult;
|
|
2521
2612
|
try {
|
|
2522
|
-
diffResult = await computeGitDiff(input.repo_path, input.base
|
|
2613
|
+
diffResult = await computeGitDiff(input.repo_path, input.base, {
|
|
2614
|
+
allowOutsideCwd: input.allow_outside_cwd
|
|
2615
|
+
});
|
|
2523
2616
|
} catch (e) {
|
|
2524
2617
|
return errorResult(e instanceof Error ? e.message : String(e));
|
|
2525
2618
|
}
|
|
@@ -2548,7 +2641,8 @@ ${diffResult.diff}`;
|
|
|
2548
2641
|
dTag: toDTag(input.capability),
|
|
2549
2642
|
kindOffset: input.kind_offset,
|
|
2550
2643
|
timeoutMs: Math.min(input.timeout_secs, MAX_TIMEOUT_SECS) * 1e3,
|
|
2551
|
-
maxPriceLamports: input.max_price_lamports
|
|
2644
|
+
maxPriceLamports: input.max_price_lamports,
|
|
2645
|
+
toolName: "submit_diff_review"
|
|
2552
2646
|
});
|
|
2553
2647
|
}
|
|
2554
2648
|
}),
|
|
@@ -2590,30 +2684,17 @@ ${diffResult.diff}`;
|
|
|
2590
2684
|
);
|
|
2591
2685
|
return errorResult(text);
|
|
2592
2686
|
}
|
|
2593
|
-
const
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
)
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
`Capability "${input.capability}" from "${safeProviderName}" costs ${formatAssetAmount(cardAsset, BigInt(price))}.${gasLine}
|
|
2605
|
-
|
|
2606
|
-
To confirm, call buy_capability again with max_price_lamports set (e.g. ${price} or higher).`,
|
|
2607
|
-
"text"
|
|
2608
|
-
);
|
|
2609
|
-
return {
|
|
2610
|
-
content: [
|
|
2611
|
-
{
|
|
2612
|
-
type: "text",
|
|
2613
|
-
text
|
|
2614
|
-
}
|
|
2615
|
-
]
|
|
2616
|
-
};
|
|
2687
|
+
const priceGate = await confirmPriceGate({
|
|
2688
|
+
agent,
|
|
2689
|
+
providerLabel: sanitizeField(provider.name || input.provider_npub, 64),
|
|
2690
|
+
capability: input.capability,
|
|
2691
|
+
price: card.payment?.job_price ?? 0,
|
|
2692
|
+
asset: assetFromCardPayment(card.payment),
|
|
2693
|
+
maxPriceLamports: input.max_price_lamports,
|
|
2694
|
+
toolName: "buy_capability"
|
|
2695
|
+
});
|
|
2696
|
+
if (priceGate) {
|
|
2697
|
+
return priceGate;
|
|
2617
2698
|
}
|
|
2618
2699
|
const expectedRecipient = card.payment?.chain === "solana" ? card.payment.address : void 0;
|
|
2619
2700
|
if (agent.solanaKeypair && !expectedRecipient) {
|
|
@@ -2682,7 +2763,7 @@ ${sanitized.text}`
|
|
|
2682
2763
|
},
|
|
2683
2764
|
onFeedback: payHandler.onFeedback,
|
|
2684
2765
|
onError(error) {
|
|
2685
|
-
reject
|
|
2766
|
+
rejectWithProviderError(reject, error);
|
|
2686
2767
|
},
|
|
2687
2768
|
onTimeout(timeoutMs) {
|
|
2688
2769
|
reject(new JobWaitTimeoutError(timeoutMs));
|