Build
MCP server
Four tools that let an LLM agent verify a counterparty and pay through the metered pay method.
The bound-near MCP server gives an LLM agent the same checks a service makes, plus a metered way to pay. It runs on stdio and keeps stdout pure JSON-RPC.
Client config
For Claude Desktop, Cursor and any other client that launches stdio servers:
{
"mcpServers": {
"bound-near": {
"command": "npx",
"args": ["-y", "@bound/near", "mcp"]
}
}
}To let the agent pay, pass its pay-only key in the server's env:
{
"mcpServers": {
"bound-near": {
"command": "npx",
"args": ["-y", "@bound/near", "mcp"],
"env": {
"BOUND_NEAR_AGENT_ID": "<agent>.testnet",
"BOUND_NEAR_AGENT_KEY": "ed25519:<function-call private key>"
}
}
}
}Use only the agent's function-call key here, never a full-access key. NEAR lets that key call only pay on the Bound contract, so even a prompt-injected model holding it can at worst spend the float, and only to recipients the operator allowlisted.
Tools
| Tool | Input | Read-only |
|---|---|---|
near_verify_agent | { agent } | yes |
near_get_certificate | { cert_id } | yes |
near_check_agent_keys | { agent } | yes |
near_pay | { cert_id, to, amount, memo? } | no; needs the agent key env |
agent and to must be NEAR account ids. amount is a decimal USDC string with up to 6 decimals, such as "2.5". memo is at most 64 characters. Results are JSON text; a failure comes back with isError: true and Error: ....
near_verify_agent
Calls verify(agent) and returns { agent, deal, reason, certificate }. Amounts in certificate are formatted as USDC: cert_id, status, agent, operator, auditor, bound, reserve_locked, reserve_promised, auditor_allocation, coverage, coverage_note, spent, float, float_cap, allowlist, halted, expires_at and claim_window_closes_at (ISO times), settlement, unit. Deal only when deal is true.
near_get_certificate
Calls get_cert(cert_id) and returns the same formatted certificate, or an error when there is none.
near_check_agent_keys
Reads the access keys and the code fields of the account. Returns agent, contract, confined, keys_confined, code_hash, global_contract_hash, global_contract_account_id, no_contract_code, no_local_code, global_contract and the raw keys. Call it together with near_verify_agent: the contract cannot check confinement itself.
near_pay
Signs pay with the agent key and returns { ok, tx, explorer, error, logs }. The contract's refusal text comes back in error, for example FloatExceeded or RecipientNotAllowed. Without the env variables it returns an error saying near_pay is disabled.
A recorded run
near/evidence/mcp-agent-mwfnc6q8.txt records an agent driving these tools against cert 1: it confined itself to a pay-only key, paid 1 to the allowlisted service, was refused with FloatExceeded for 500 and with RecipientNotAllowed for the treasury, near_check_agent_keys reported it confined, and its float dropped by exactly 1. Every write (the key swap and the three payments) has a transaction; the reads are plain view calls. No language model runs in that script: the calls are the exact tool calls an LLM agent holding that key could make, so a prompt-injected model is refused the same way. See near/evidence.
Embed it
createNearMcpServer returns an McpServer from @modelcontextprotocol/sdk; add that package to your own dependencies to import a transport.
import { createNearMcpServer } from "@bound/near/mcp";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = createNearMcpServer({
contractId: "bound-v3.boundproto-nh26.testnet", // default
// rpc, provider, agentId, agentKey are optional
});
await server.connect(new StdioServerTransport());- From a clone of the repository:
pnpm --silent near:mcp, with client config{"command": "pnpm", "args": ["--silent", "--dir", "<repo>", "near:mcp"]}. pnpm --silent near:mcp-smokeconnects a client in-process and calls every read tool against testnet.