Absorb
Walkthrough

From an OpenAPI specification to a governed agent surface

Every screen and command below comes from the shipped binary running against a ticketing API. Same binary in every edition, same output in your environment.

In your OpenAPI spec and a policy fileOut a reviewed tool surface, served over MCP and audited
  1. 1Compileabsorb diff
  2. 2Reviewabsorb diff --html
  3. 3Serveabsorb serve
  4. 4Approveapprove: true
  5. 5Probeabsorb probe

1Define the policy, compile the surface

The policy is the contract your security function reviews: which operations an agent may call, which fields come back, and which writes need a human. Nothing outside it is emitted.

agent.yamlreviewed like IAM
allow beats nothing, deny beats allow, writes must be named
allow:
  - GET /tickets
  - GET /tickets/{id}
  - POST /tickets/{id}/comment

deny:
  - DELETE
  - /admin/**
  - /internal/**

fields:
  Ticket:
    include: [id, status, priority, summary, blocker_flag, assignee.name]
    exclude: [customer_email, ssn, raw_description]

params:
  deny: [include_deleted, internal]

limits:
  response_bytes: 8000
  max_tools: 12

tools:
  POST /tickets/{id}/comment:
    name: add_comment
    approve: true
    conditions:
      - arg: id
        from_claim: ticket_scope
absorb diff15 to 3 tools
$ absorb diff --spec tickets.yaml --policy agent.yaml

  15 operations to 3 tools, 12 withheld
  contract 7.1 KB to 1.1 KB  (-83.8%)

Exposed (3)
  + search_tickets  GET    /tickets
  + get_ticket      GET    /tickets/{id}
  + add_comment     POST   /tickets/{id}/comment

Withheld (12)
  - DELETE  /tickets/{id}      denied by DELETE
  - GET     /admin/audit       denied by /admin/**
  ... 10 more

3Publish to the agents your teams already use

One configuration file defines the upstream, its credentials, the agents allowed to connect and where audit records go. Secrets are referenced from the environment or a mounted file, never held in the policy.

absorb.yamlsecrets by reference
upstream:
  url: https://tickets.internal.example.com/api
  credentials:
    - type: bearer
      token: ${env:TICKETS_API_TOKEN}

agents:
  - id: renewals-bot
    token_sha256: 5c3a1f0b...
    tools: ["get_*", "search_*", "add_comment"]

admins:
  - id: alice
    token_sha256: 9f1c22de...

approvals:
  slack:
    webhook_url: ${env:SLACK_WEBHOOK_URL}
    signing_secret: ${env:SLACK_SIGNING_SECRET}

audit:
  path: /var/log/absorb/audit.jsonl
  export:
    url: https://collector.internal.example.com/absorb
    secret: ${env:AUDIT_EXPORT_SECRET}
client configMCP
Cursor, Windsurf, VS Code and any MCP client that takes a URL
{ "mcpServers": { "tickets": { "url": "https://absorb.internal:8080/mcp" } } }

Claude Desktop, running the binary locally
{
  "mcpServers": {
    "tickets": {
      "command": "/usr/local/bin/absorb",
      "args": ["serve", "--stdio", "--config", "/etc/absorb/absorb.yaml",
               "--spec", "/etc/absorb/tickets.yaml",
               "--policy", "/etc/absorb/agent.yaml"]
    }
  }
}
  • Streamable HTTP
  • Legacy SSE
  • stdio
  • Pod sidecar
  • Behind your gateway
  • Single binary on a VM

4Route sensitive operations to a human decision

Mark an operation with approve: true, or set a threshold such as approve_above: 1000 on an amount. The call is held before it reaches your API. On approval it runs once and the result returns to the agent.

  1. Agent callsadd_comment
  2. Heldbefore your API
  3. RoutedSlack, ServiceNow, webhook or admin queue
  4. Human decidesapprover, channel and arguments recorded
  5. Runs onceresult returned to the agent
Admin dashboard with one call waiting for approval, its arguments, and Approve and Deny buttons
The authorization queue, live audit trail, and each agent with its permitted scope
what the agent seesheld then approved
agent calls a tool the policy holds for approval
{
  "status": "pending_approval",
  "approval_id": "apr_58c74db86fff9c8830",
  "expires": "2026-09-22T21:08:30Z",
  "message": "A human must approve this call before it runs.
              Tell the user it is waiting for approval, then call
              absorb_check_approval with this approval_id."
}

after a human approves, the same agent asks again
{ "id": "c_42", "body": "noted", "created_at": "2026-09-22T21:00:12Z" }
[absorb] approved by alice via admin

5Detect specification drift before your agents do

The spec is not the runtime. absorb probe calls every exposed read tool once against the real API and compares the response with the spec. It exits non-zero, so it can gate a deployment in your pipeline.

  • Wrong typesinteger in the spec, string in production
  • Missing fieldsdeclared but never returned
  • Error bodies as 200failures disguised as success
  • Emptied responsesthe policy would strip everything
$ absorb probe --spec tickets.yaml --policy agent.yaml --config absorb.yaml

absorb probe  https://tickets.internal.example.com/api

  OK            search_tickets  200  1.5 KB  61ms
      undeclared fields (dropped by projection): debug
  OK            get_ticket      200  489 B   44ms
  DRIFT         list_comments   200  2.1 KB  58ms
      ~ [].author.id: expected integer, got string (x12)
  ERROR BODY    get_billing     200  144 B   39ms
      HTTP 200 but the body matches ResponseNOTOK: message="NOTOK"
  SKIPPED       add_comment
      writes are not probed

  2 ok, 1 drift, 0 empty, 1 error bodies, 0 failed, 1 skipped

In production the same mismatches are recorded in the audit log and in the absorb_drift_values_total metric, so they surface on a dashboard rather than in a support ticket.

Begin with one specification

A 45 minute session: we compile your API, walk your security team through the review document, and hand over the policy and deployment manifest.