Take actions in your product

An agent that operates your product for one end-user through your product's own REST API. A customer types "refund my last invoice" in your billing app; the agent looks the invoice up and issues the refund through your API, pausing for a human on the refund itself.

The mental model, in three moves:

  • Your product becomes the agent's tools. Describe your REST endpoints once (custom tools); the agent sees each as a named tool. m8tes makes the call server-side, with your secret injected and never seen by the agent.
  • It acts as one end-user. Pass a user_id and the agent, its runs, memory, and permissions are scoped to that customer (Users). Isolation is strict, with no fallback to account data.
  • Writes wait for a human. A custom tool asks for approval before each use until you mark it trusted.

Wrap your REST API

Each endpoint you describe becomes a tool the agent can call by name:

Python

Put {param} in a path to make it an argument (/invoices/{id}); any other arguments the agent passes go to the query string (GET/DELETE) or the JSON body (POST/PUT/PATCH). Auth methods and path rules: Custom tools.

Scope it to an end-user

If every customer hits your API with the same key, keep one account-level server and scope at the agent and run (next section). If each customer has their own credentials, create one server per customer:

Python

For your customers' Gmail, Slack, and other catalog apps, use per-end-user OAuth instead (Tools).

Attach it and run it

Reference the server's slug in an agent's tools, then run for a customer:

Python

The agent sees the tool as mcp__cmcp-<id>__create_refund; you only ever use the slug. Custom slugs attach to an agent — unlike catalog tools, they can't be passed per task or per run (a run-level tools list containing a custom slug is rejected). Give the agent its custom tools once, at create or update.

Keep a human in the loop

Read-only (GET) tools run without asking, in every permission mode — they can't change anything. Writes ask before each use. To let a write run unattended, mark its server trusted:

Python

An untrusted write pauses for approval; in an unattended run with no one to approve, it is marked needs-approval and never silently fires. Handle approvals inline with runs.wait(...), or from your inbox or Slack. See Human-in-the-loop.

What's guaranteed (full detail): egress is server-side and IP-pinned (the agent never connects to your service directly; private and metadata addresses are blocked); your secret is encrypted at rest and never reaches the agent; a server is visible only to its owner and, when scoped, its user_id end-user.

End-to-end: refund an invoice with approval

A billing product lets its agent look up invoices on its own but requires a human to approve refunds, all for one customer:

Python

The lookup ran on its own; the refund paused for on_approval. Surface req.tool_input in your UI so a human can decide before you return "allow" or "deny".

Next: Your API as MCP tools · Custom tools · Users · Human-in-the-loop · Going live

Was this page helpful?