run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
148 lines • 6.45 kB
JavaScript
/**
* `grants` namespace — per-project capability grants and the grant keys minted
* against them.
*
* A grant is the permission row (this principal may do this capability on this
* project until this date). A grant key is a credential minted against exactly
* one grant: narrower than it, spend-capped, expiring, revocable on its own,
* never an owner. An agent with its own wallet needs only the grant (it signs
* via SIWX); an agent without one gets a grant key.
*
* The practical reason keys exist on the client: project API keys are
* stateless JWTs handed out once at create and never re-issued, so an agent
* that loses local state has no way back into its own project. The owner still
* holds a wallet, and SIWX is enough to mint a fresh grant key.
*
* Every call requires the caller to be an active owner of the project's org.
* Exposed both unscoped (`r.grants.create(projectId, …)`) and project-scoped
* (`r.project(id).grants.create(…)`), mirroring `r.functions` / `r.jobs`.
*/
import { gateSecret } from "../secret-gate.js";
import { LocalError } from "../errors.js";
function grantKeyBody(input) {
const body = {};
if (input.kind !== undefined)
body.kind = input.kind;
if (input.scope !== undefined)
body.scope = input.scope;
if (input.spendCap !== undefined)
body.spend_cap = input.spendCap;
if (input.expiresAt !== undefined)
body.expires_at = input.expiresAt;
if (input.publicSubject !== undefined)
body.public_subject = input.publicSubject;
return body;
}
function projectPath(projectId) {
return `/projects/v1/${encodeURIComponent(projectId)}`;
}
export class Grants {
client;
constructor(client) {
this.client = client;
}
/**
* Issue a capability grant to a wallet for a project
* (`POST /projects/v1/:project_id/grants`). With `key`, also mints the
* grant's first grant key in the same transaction; its `key.token` is
* returned **once** — persist it immediately, or rotate for a new one.
*/
async create(projectId, input) {
gateSecret(this.client, "grants.create", projectId, input);
if (!projectId) {
throw new LocalError("grants.create requires a projectId", "creating project grant");
}
if (!input?.wallet) {
throw new LocalError("grants.create requires { wallet }", "creating project grant");
}
if (!input?.capability) {
throw new LocalError("grants.create requires { capability }", "creating project grant");
}
const body = {
wallet: input.wallet,
capability: input.capability,
};
if (input.policy !== undefined)
body.policy = input.policy;
if (input.expiresAt !== undefined)
body.expires_at = input.expiresAt;
if (input.key !== undefined)
body.key = grantKeyBody(input.key);
return this.client.request(`${projectPath(projectId)}/grants`, {
method: "POST",
body,
context: "creating project grant",
});
}
/**
* List a project's grants with their grant keys nested
* (`GET /projects/v1/:project_id/grants`). Never returns a token or any
* secret material.
*/
async list(projectId) {
if (!projectId) {
throw new LocalError("grants.list requires a projectId", "listing project grants");
}
return this.client.request(`${projectPath(projectId)}/grants`, {
method: "GET",
context: "listing project grants",
});
}
/**
* Revoke a capability grant and every grant key minted against it
* (`DELETE /projects/v1/:project_id/grants/:grant_id`).
*/
async revoke(projectId, grantId) {
if (!projectId) {
throw new LocalError("grants.revoke requires a projectId", "revoking project grant");
}
if (!grantId) {
throw new LocalError("grants.revoke requires a grantId", "revoking project grant");
}
return this.client.request(`${projectPath(projectId)}/grants/${encodeURIComponent(grantId)}`, { method: "DELETE", context: "revoking project grant" });
}
/**
* Mint another grant key against an existing grant
* (`POST /projects/v1/:project_id/grants/:grant_id/keys`). `key.token` is
* returned **once**.
*/
async createKey(projectId, grantId, input = {}) {
gateSecret(this.client, "grants.createKey", projectId, grantId, input);
if (!projectId) {
throw new LocalError("grants.createKey requires a projectId", "minting grant key");
}
if (!grantId) {
throw new LocalError("grants.createKey requires a grantId", "minting grant key");
}
return this.client.request(`${projectPath(projectId)}/grants/${encodeURIComponent(grantId)}/keys`, { method: "POST", body: grantKeyBody(input), context: "minting grant key" });
}
/**
* Revoke one grant key (`DELETE /projects/v1/:project_id/grant-keys/:key_id`).
* The grant and its other keys stay.
*/
async revokeKey(projectId, keyId) {
if (!projectId) {
throw new LocalError("grants.revokeKey requires a projectId", "revoking grant key");
}
if (!keyId) {
throw new LocalError("grants.revokeKey requires a keyId", "revoking grant key");
}
return this.client.request(`${projectPath(projectId)}/grant-keys/${encodeURIComponent(keyId)}`, { method: "DELETE", context: "revoking grant key" });
}
/**
* Replace a grant key (`POST /projects/v1/:project_id/grant-keys/:key_id/rotate`):
* revokes it and mints a fresh one against the same grant with the same
* kind, scope, cap and expiry. The new `key.token` is returned **once**.
*/
async rotateKey(projectId, keyId) {
gateSecret(this.client, "grants.rotateKey", projectId, keyId);
if (!projectId) {
throw new LocalError("grants.rotateKey requires a projectId", "rotating grant key");
}
if (!keyId) {
throw new LocalError("grants.rotateKey requires a keyId", "rotating grant key");
}
return this.client.request(`${projectPath(projectId)}/grant-keys/${encodeURIComponent(keyId)}/rotate`, { method: "POST", context: "rotating grant key" });
}
}
//# sourceMappingURL=grants.js.map