Custom Tools

Connect your own service's API as tools your agents can call. You define typed endpoints; m8tes exposes each one to the agent as a named tool and makes the call for you: IP-pinned, with your secret injected server-side and never exposed to the agent.

Use custom tools when the app you need isn't in the catalog and you already have a REST API for it.

m8tes runs the MCP server for you — you supply the endpoints, your agents get typed tools.

Building this into your own product, one agent per customer? See Your API as MCP tools for the end-to-end embed recipe, or Take actions in your product for the single-tenant version.

Create a custom tool

Python

Each entry in tool_defs becomes a tool the agent sees by name (get_invoice, create_refund). 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). In the dashboard each tool_def shows as an Action ("Add action" adds one), but the SDK param name stays tool_defs.

Attach to an agent

Reference the server's slug in the agent's tools list, alongside catalog tools:

Python

In the dashboard, open Apps → Add custom tool to create one, then enable it on an agent from its Configure → Custom tools section.

An agent can also propose a custom tool mid-run: it surfaces as a permission approval the owner approves or denies before the tool is created.

Authentication

The secret is encrypted at rest and injected by m8tes when it makes the call. Pick the auth_type your API expects:

auth_typeWhat m8tes sendsExtra auth_config
nonenothingnone
bearerAuthorization: Bearer <secret>none
oauth_tokenAuthorization: Bearer <secret>none
custom_header<header>: <secret>{"header_name": "X-API-Key"}
api_key_in_url?<param>=<secret>{"query_param": "api_key"}
Python

Permissions

Read-only tools never ask. A GET tool def runs immediately, in every permission mode — it can't mutate anything, so it is treated like any other read. Plan for this: if a lookup against your API is itself sensitive, don't model it as GET, and don't rely on an approval prompt to gate it.

Write tools ask before each use, so a human stays in the loop on anything that changes state. To let a write run unattended (in scheduled tasks, webhook/email triggers, or API runs where nobody is watching), mark the server trusted: set the "Run without asking each time" toggle when you add it, or pass auto_approve=true to client.mcp_servers.create/update. An untrusted write pauses for approval; in an unattended run with no one to approve, the run is marked needs-approval, never silently failed.

Trust is per server, not per tool, so the usual shape is two servers against the same API: one holding the GET defs, one holding the writes you want gated.

Security model

  • Egress is server-side and IP-pinned. The agent never connects to your service directly. m8tes resolves and pins the host to a validated public IP per call (no DNS-rebind), disables redirects, and caps the response. Private/metadata addresses are blocked.
  • The secret never reaches the agent. It is injected into the outbound request by m8tes and scrubbed from any echoed response. API responses carry only has_secret, never the value.
  • Strictly scoped. A custom tool is visible only to its owner (and, for multi-tenant setups, the user_id end-user it was created for).

Manage

Python

Next: Your API as MCP tools · Take actions in your product · Human-in-the-loop · Users

Was this page helpful?