MCP Quickstart

Your agent can delete a production database in 9 seconds. Add a gate.

Attest checks every MCP tool call against a scoped credential before it executes. No credential, no action. Wrong scope, blocked. Every call logged with a signed receipt.

This already happened. A coding agent with unrestricted tool access dropped a production database and its backups in a single session. No approval step, no scope check, no audit trail. The entire recovery was manual.

Attest would have blocked the DROP DATABASE call at the tool boundary. The agent had no credential with db:admin scope, so the call never executes.

Without Attest
server.tool("run_sql", schema, async ({ query }) => {
  // Agent calls this with anything.
  // DROP TABLE? Sure. DELETE FROM users? Sure.
  return db.execute(query);
});

The agent has full access to every tool. Nothing checks whether it should be allowed to run that specific action.

With Attest
protectedServer.tool("run_sql", schema, async ({ query }) => {
  // Only executes if caller holds db:read or db:write.
  // db:admin required for DDL. No credential = blocked.
  return db.execute(query);
});

Every call checked against the caller's credential scope. Wrong scope or no credential: rejected before your handler runs.

1

Install

npm install @attest-dev/sdk @modelcontextprotocol/sdk
2

Wrap your MCP server

Four lines. Your existing tools keep working. Attest intercepts each call and checks the caller's credential before your handler runs.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { withAttest } from "@attest-dev/sdk/mcp";

const server = new McpServer({ name: "my-tools", version: "1.0.0" });
const attest = withAttest(server, {
  issuerUri: "https://api.attestdev.com",
});

// This tool only executes if the caller holds "email:send" scope
attest.tool("send_email", { to: { type: "string" }, body: { type: "string" } },
  async ({ to, body }) => {
    await sendEmail(to, body);
    return { content: [{ type: "text", text: `Sent to ${to}` }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
3

Issue a credential

Create a workspace, then issue a credential with only the scopes the agent needs. Nothing more.

# Create a workspace (one-time setup)
curl -s -X POST https://api.attestdev.com/v1/orgs \
  -H "Content-Type: application/json" \
  -d '{"name":"acme-corp"}'
# Returns: { "id": "org_...", "api_key": "att_live_..." }

# Issue a credential scoped to email:send only
curl -s -X POST https://api.attestdev.com/v1/credentials \
  -H "Authorization: Bearer att_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "support-bot",
    "user_id": "alice@acme.com",
    "scope": ["email:send"],
    "ttl": 3600
  }'

The credential is a signed JWT. It expires in 1 hour. The agent can call send_email but cannot call run_sql, delete_user, or anything else outside its scope.

4

See what happened

Every allowed and blocked call is recorded. Open the dashboard to see the audit trail, or export a signed evidence packet and verify it independently.

# Export the evidence packet for this task
curl -s https://api.attestdev.com/v1/evidence/task_8f3a...c91d \
  -H "Authorization: Bearer att_live_..." \
  -o evidence.json

# Verify it (or use attestdev.com/verify in browser)
npx @attest-dev/sdk verify evidence.json

What Attest does on every tool call

Checks scope

Extracts the credential from the call context. If the required scope is missing, the call is rejected before your handler runs.

Logs the action

Records who called what, with which credential, at what time. Every entry is hash-chained so the log cannot be silently altered.

Signs a receipt

Produces a signed evidence packet you can export, verify independently, and hand to an auditor months later.