New:Socket for Asana Is Now Available.Learn more
Get Started

@grantor/mcp

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@grantor/mcp - npm Package Compare versions

Comparing version
0.1.4
to
0.1.5
+1
-1
package.json
{
"name": "@grantor/mcp",
"mcpName": "com.chaingrantor/grantor-mcp",
"version": "0.1.4",
"version": "0.1.5",
"description": "Grantor permission broker for multi-agent frameworks \u2014 grant, delegate, check, revoke bounded capabilities over MCP. No authorization server anywhere.",

@@ -6,0 +6,0 @@ "license": "SEE LICENSE IN LICENSE",

@@ -22,2 +22,3 @@ // chain.js — the on-chain seam: viem clients + the minimal inline ABI

"function delegationEpoch(uint256 id, bytes32 label) view returns (uint64)",
"function tenants(uint256 id) view returns (uint8 tier, uint64 periodEnd, uint256 balance, uint32 appCount, uint32 agentCount, uint32 userCount)",
"event TenantCreated(uint256 indexed id, address indexed owner, uint8 tier)",

@@ -101,2 +102,12 @@ ]);

async tenantBalance(tenant) {
const [, , balance] = await publicClient.readContract({
address: cfg.registry,
abi: REGISTRY_ABI,
functionName: "tenants",
args: [BigInt(tenant)],
});
return balance;
},
async isAgentKey(tenant, commitmentHex) {

@@ -103,0 +114,0 @@ return publicClient.readContract({

@@ -99,2 +99,3 @@ #!/usr/bin/env node

tier: flags.tier !== undefined ? Number(flags.tier) : undefined,
tenant: flags.tenant !== undefined ? Number(flags.tenant) : undefined,
emitRpc: flags["emit-rpc"],

@@ -101,0 +102,0 @@ out: flags.out,

@@ -89,2 +89,32 @@ // setup.js — the `setup-sandbox` verb: create+fund+enroll+pre-bump a real

/**
* Poll a chain read until `isOk(value)` holds, instead of aborting on the
* first stale answer. Exists because load-balanced public RPCs
* (mainnet.base.org) route consecutive reads to different replicas: the
* 2026-08-21 sandbox provisioning FALSE-ABORTED at step 5/8 when the
* post-`drawPeriod` `status()` read hit a replica that hadn't seen the tx —
* the money was fine, the assert wasn't. Still fails closed after
* `attempts` reads; the error names `--tenant` resume so an operator who
* DOES hit the exhausted case knows the recovery path.
*/
export async function retryReadAssert(label, read, isOk, { attempts = 5, delayMs = 3000, sleep } = {}) {
const wait = sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
let value;
for (let i = 1; i <= attempts; i++) {
value = await read();
if (isOk(value)) return value;
if (i < attempts) {
console.error(
`grantor-mcp setup-sandbox: ${label} not confirmed yet (read ${i}/${attempts} saw ${value}) — retrying in ${delayMs}ms (replica lag?)`,
);
await wait(delayMs);
}
}
throw new Error(
`grantor-mcp setup-sandbox: ${label} still unconfirmed after ${attempts} reads (last saw ${value}) — ` +
"if the RPC is load-balanced this can be replica lag on a transaction that DID land; " +
"re-run with --tenant <id> to resume without re-paying",
);
}
/**
* Create+fund+enroll+pre-bump one real on-chain tenant and write a

@@ -100,2 +130,8 @@ * Task-1-shaped `sandbox-config.json`. Runs against ANY RPC — local devnet

* @param {number} [args.tier] tier to create the tenant at (default 1).
* @param {number} [args.tenant] EXISTING tenant id to resume an interrupted
* run against: skips `createTenant`, skips funding entirely if the tenant
* is already Active, and otherwise tops up only the shortfall between the
* target (`tierFee * periods`) and the balance already on the tenant. The
* recovery path for the replica-lag false-abort class (see
* `retryReadAssert`) — never pays twice for work that already landed.
* @param {string} [args.emitRpc] RPC url written into the output config in

@@ -107,5 +143,8 @@ * place of the chain config's own `rpcUrl` — lets a mainnet run transact

* @param {Record<string,string>} [args.env] defaults to `process.env`.
* @param {(cfg: object, key: string) => object} [args.makeChainImpl] chain-seam
* factory, defaults to `makeChain` — injection point for the unit tests'
* fake chain (the live path is proven by `e2e.sh`).
* @returns {Promise<object>} the written sandbox config.
*/
export async function setupSandbox({ chainConfigPath, periods = 12, tier = 1, emitRpc, out, env = process.env }) {
export async function setupSandbox({ chainConfigPath, periods = 12, tier = 1, tenant, emitRpc, out, env = process.env, makeChainImpl }) {
// Cheap, chain-independent guards first — no file read, no network, no

@@ -117,2 +156,8 @@ // key required to fail fast on a bad invocation.

}
if (tenant !== undefined) {
const tenantNum = Number(tenant);
if (!Number.isInteger(tenantNum) || tenantNum < 1) {
throw new Error(`grantor-mcp setup-sandbox: --tenant must be a positive integer (an existing tenant id to resume), got ${tenant}`);
}
}
if (!chainConfigPath) {

@@ -132,28 +177,57 @@ throw new Error("grantor-mcp setup-sandbox: --chain-config <path> is required");

const chain = makeChain(config, key);
const chain = (makeChainImpl ?? makeChain)(config, key);
console.error(`grantor-mcp setup-sandbox: 2/8 creating tenant (owner=${owner}, tier=${tier})`);
const tenantId = await chain.createTenant(owner, tier);
console.error(`grantor-mcp setup-sandbox: tenant ${tenantId} created`);
console.error(`grantor-mcp setup-sandbox: 3/8 reading tier ${tier} fee`);
const fee = await chain.tierFee(tier);
if (fee > 0n) {
const amount = fee * BigInt(periodsNum);
let tenantId;
let alreadyActive = false;
if (tenant !== undefined) {
tenantId = BigInt(Number(tenant));
// Single probe, not retried: a stale not-Active answer here cannot cost
// money — the shortfall topUp below only ever prepays the same tenant,
// and a redundant drawPeriod reverts on the contract's own
// "period active" guard, so the worst case is one reverted tx and a
// clean re-run that then sees Active.
const probed = await chain.status(tenantId);
alreadyActive = probed === 1;
console.error(
`grantor-mcp setup-sandbox: 4/8 approving + topping up ${amount} (fee=${fee} x periods=${periodsNum}) — ` +
"the caller must already hold this much USDC; setup-sandbox never mints",
`grantor-mcp setup-sandbox: 2/8 resuming with existing tenant ${tenantId} (status=${probed}) — createTenant skipped`,
);
await chain.approveAndTopUp(config.usdc, tenantId, amount);
} else {
console.error("grantor-mcp setup-sandbox: 4/8 tier fee is 0 — skipping approve+topUp");
console.error(`grantor-mcp setup-sandbox: 2/8 creating tenant (owner=${owner}, tier=${tier})`);
tenantId = await chain.createTenant(owner, tier);
console.error(`grantor-mcp setup-sandbox: tenant ${tenantId} created`);
}
console.error(`grantor-mcp setup-sandbox: 5/8 drawing first period for tenant ${tenantId}`);
await chain.drawPeriod(tenantId);
const status = await chain.status(tenantId);
if (status !== 1) {
throw new Error(`grantor-mcp setup-sandbox: tenant ${tenantId} status is ${status} after drawPeriod, expected 1 (Active)`);
if (alreadyActive) {
console.error("grantor-mcp setup-sandbox: 3/8-5/8 tenant is already Active — skipping funding (no topUp, no drawPeriod)");
} else {
console.error(`grantor-mcp setup-sandbox: 3/8 reading tier ${tier} fee`);
const fee = await chain.tierFee(tier);
if (fee > 0n) {
const target = fee * BigInt(periodsNum);
// On resume, credit whatever an interrupted run already deposited —
// top up only the shortfall, never the full target again.
const balance = tenant !== undefined ? await chain.tenantBalance(tenantId) : 0n;
const amount = target > balance ? target - balance : 0n;
if (amount > 0n) {
console.error(
`grantor-mcp setup-sandbox: 4/8 approving + topping up ${amount} (fee=${fee} x periods=${periodsNum}${balance > 0n ? `, minus ${balance} already on the tenant` : ""}) — ` +
"the caller must already hold this much USDC; setup-sandbox never mints",
);
await chain.approveAndTopUp(config.usdc, tenantId, amount);
} else {
console.error(`grantor-mcp setup-sandbox: 4/8 tenant balance ${balance} already covers the ${target} target — skipping approve+topUp`);
}
} else {
console.error("grantor-mcp setup-sandbox: 4/8 tier fee is 0 — skipping approve+topUp");
}
console.error(`grantor-mcp setup-sandbox: 5/8 drawing first period for tenant ${tenantId}`);
await chain.drawPeriod(tenantId);
await retryReadAssert(
`tenant ${tenantId} status after drawPeriod (expected 1/Active)`,
() => chain.status(tenantId),
(s) => s === 1,
);
console.error(`grantor-mcp setup-sandbox: tenant ${tenantId} is Active`);
}
console.error(`grantor-mcp setup-sandbox: tenant ${tenantId} is Active`);

@@ -165,6 +239,7 @@ const principalKey = generatePrivateKey();

await chain.registerAgentKey(tenantId, commitment);
const isKey = await chain.isAgentKey(tenantId, commitment);
if (!isKey) {
throw new Error(`grantor-mcp setup-sandbox: registerAgentKey landed but isAgentKey is still false for ${principalAddress}`);
}
await retryReadAssert(
`isAgentKey for ${principalAddress} after registerAgentKey`,
() => chain.isAgentKey(tenantId, commitment),
(isKey) => isKey === true,
);
console.error("grantor-mcp setup-sandbox: agent key registered");

@@ -175,6 +250,7 @@

await chain.bumpEpoch(tenantId, demoRevokedLabel);
const epoch = await chain.delegationEpoch(tenantId, demoRevokedLabel);
if (epoch !== 1) {
throw new Error(`grantor-mcp setup-sandbox: delegationEpoch after bumpEpoch is ${epoch}, expected 1`);
}
const epoch = await retryReadAssert(
`delegationEpoch for the demo-revoked label after bumpEpoch (expected 1)`,
() => chain.delegationEpoch(tenantId, demoRevokedLabel),
(e) => e === 1,
);
console.error(`grantor-mcp setup-sandbox: epoch bumped to ${epoch}`);

@@ -181,0 +257,0 @@