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.7
to
0.1.8
+1
-1
package.json
{
"name": "@grantor/mcp",
"mcpName": "com.chaingrantor/grantor-mcp",
"version": "0.1.7",
"version": "0.1.8",
"description": "Grantor permission broker for multi-agent frameworks \u2014 grant, delegate, check, revoke bounded capabilities over MCP, and wrap any stdio MCP server with enforced permissions. No authorization server anywhere.",

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

@@ -175,6 +175,15 @@ # @grantor/mcp

is.
- **Metering is broker-local.** `max_uses`/`remaining_uses` live in this
process's state file (`~/.grantor-mcp/state.json` by default), not
on-chain. Two brokers, or two runs against the same config without a
shared state file, do not share a budget.
- **Metering is broker-local — and, within one broker, atomic and
conserving.** `max_uses`/`remaining_uses` live in this process's state
file (`~/.grantor-mcp/state.json` by default), not on-chain. Inside one
broker process the meter holds two invariants: `check` reserves the use
in the same synchronous step as the balance gate (two concurrent
last-use calls can never both pass; a denied call refunds; a crash
between authorization and the tool's side effect loses a use — it can
never double-spend one), and `delegate` TRANSFERS uses from the parent's
live pool (a tree of delegations can never hold more aggregate uses than
the root grant; a drained parent refuses to delegate). Across processes
the meter is NOT shared or atomic: run one broker per state file.
`to`-mode links (external holders) carry no broker meter at all — their
bound is the signed per-path `max_uses` caveat alone.

@@ -181,0 +190,0 @@ None of this is a limitation you have to accept — it's what changes the

@@ -29,2 +29,14 @@ // check.js — verify a capability deed (broker-held child OR a presented

if (child.usesRemaining <= 0) return deny("UsesExhausted", "this grant's local use budget is spent");
// ATOMIC CONSUMPTION: reserve the use HERE, synchronously, before any
// await. The verify below awaits a chain read — gating on the balance
// and decrementing only after that await would let two concurrent
// last-use checks both observe remaining=1 and both allow. Reserving in
// the same synchronous block as the gate makes the pair atomic under
// Node's single thread; a failed verify refunds below, so a denied call
// never consumes. Crash window: a crash between this reserve and the
// tool's side effect LOSES a use (fail-safe) — it can never double-spend
// one. Cross-PROCESS metering is not atomic: one broker process per
// state file (see README).
child.usesRemaining -= 1;
saveState(statePath, state);
chal = randomBytes(16).toString("hex"); // self-issued: broker is holder AND verifier (documented)

@@ -46,8 +58,9 @@ const base = mintUserDeed(child.userSecretKeyHex, cfg.tenant, cfg.audience, cfg.origin, chal, now + 300);

} catch (e) {
if (child) {
// Refund the reserved use — a denied call must not consume budget.
child.usesRemaining += 1;
saveState(statePath, state);
}
return deny(e.name || "Error", e.message);
}
if (child) {
child.usesRemaining -= 1;
saveState(statePath, state);
}
const out = { allow: true, sub: resolved.claims.sub, remaining_uses: child ? child.usesRemaining : null };

@@ -54,0 +67,0 @@ if (note) out.grantor_note = note;

@@ -33,3 +33,17 @@ // delegate.js — a HELD child re-delegates a narrower slice of its own grant

const parentMaxUses = Math.min(...parent.grants.map((g) => g.cav?.max_uses ?? Infinity));
const maxUses = max_uses === undefined ? parentMaxUses : Math.min(parentMaxUses, max_uses);
const requested = max_uses === undefined ? parentMaxUses : Math.min(parentMaxUses, max_uses);
// CONSERVATION: a broker-held delegation TRANSFERS uses from the parent's
// live pool rather than minting a fresh meter — otherwise N delegations of
// a 50-use grant put N×50 uses in play and the tree manufactures
// authority. The child's signed caveat AND its local meter both get the
// transferred amount (clamped to what the parent actually has left);
// narrowing vs the parent's own caveat is still checked below. `to`-mode
// links carry no broker meter (the holder is external) — their bound is
// the signed caveat alone, per-path, documented in the README.
const maxUses = to === undefined ? Math.min(requested, parent.usesRemaining) : requested;
if (to === undefined && maxUses <= 0) {
throw new Error(
"delegate: parent has no uses remaining to transfer — the pool is conserved; delegation cannot mint uses",
);
}
const toolNames = tools ?? toolNamesFromGrants(parent.grants, DEFAULT_NS);

@@ -68,2 +82,5 @@ const grants = buildToolGrants({ tools: toolNames, ns: DEFAULT_NS, exp, maxUses });

child.chain = [...parent.chain.slice(0, -1), newLink, await selfHopLink(child, cfg.tenant, newLink)];
// The transfer half of conservation: what the child received, the parent
// no longer has. Saved atomically with the child's creation (one write).
parent.usesRemaining -= maxUses;
state.children[child.id] = child;

@@ -70,0 +87,0 @@ saveState(statePath, state);

@@ -13,3 +13,7 @@ // tools.js — inspect an MCP server and SUGGEST a wrap command. This is the

// (a `delete_read_marker` is write-ish).
const WRITEY = /(write|creat|delet|remov|updat|move|renam|exec|run|shell|command|spawn|send|post|put|patch|insert|drop|kill|install|deploy|push|publish|set_|^set$|upload|edit|append|mkdir|rm_|^rm$|clear|reset|revoke|approve|transfer|sign)/i;
// Tuned against real servers: dogfooding vs @modelcontextprotocol/
// server-github (2026-08-25) showed `merge_pull_request`, `fork_repository`
// and `add_issue_comment` slipping through as "read-like" — merge/fork/add_
// are mutations. (`comment` alone stays unmatched: get_*_comments is a read.)
const WRITEY = /(write|creat|delet|remov|updat|move|renam|exec|run|shell|command|spawn|send|post|put|patch|insert|drop|kill|install|deploy|push|publish|set_|^set$|upload|edit|append|mkdir|rm_|^rm$|clear|reset|revoke|approve|transfer|sign|merge|fork|add_|^add$|submit|close|dismiss|apply)/i;

@@ -16,0 +20,0 @@ export function splitByRisk(names) {