MCP
The MCP catalog lists the MCP servers your agents may call and which tools each agent may use. A server is one of two kinds:
| Kind | How it works |
|---|---|
| Connected | Added with Connect server: a URL and, if the server needs one, an auth token. Forgebench connects to the server, reads its tools, holds the token, and serves the tools to your agents through the MCP gateway. The agent never holds the server's token. |
| Declared | Added with Add without URL: a name, a hand-typed tool list and tags. Forgebench stores no URL or token and never connects. The agent's own MCP client talks to the server; Forgebench checks which tools the agent may call. |
Both kinds share the same kill switches, bindings, ceilings and revoke.
What gets checked, and when
For a connected server, Forgebench checks every tools/call through the
MCP gateway before calling the server.
For a declared server, Forgebench checks the governed chat completion. When
the model's response has a tool_calls entry naming an MCP tool (a qualified
name like mcp_time_get_current_time), Forgebench compares the exact name
against the agent's bindings. If it matches a live binding, the agent receives
the response and calls the tool with its own client. If not, the response is
blocked.
Connect a server
Connect server opens Connect an MCP server:
Name it
Agents see its tools as
mcp_<name>_<tool>. You can't change the name later.Enter the server URL
The server's streamable-HTTP MCP endpoint, for example
https://mcp.example.com/mcp. Internal and private addresses are blocked.Choose authentication
Bearer token (sent as
Authorization: Bearer <token>), Custom header (you name the header, for exampleX-Api-Key), or No auth. Paste the token under Auth token.Test the connection
Test connection lists the server's tools without saving. Tools that look like they change data are marked changes data.
Add a description and tags, then Connect server
Saves the server with its tools. Nothing is saved if the server can't be reached or lists no tools.
The token is encrypted and never shown again, apart from its last four characters.
On a connected server's page:
- Refresh tools re-reads the tool list. Added and removed tools go live immediately as a new version.
- URL & token changes the URL or token. Forgebench tests the new settings and saves nothing if they fail. Leave the token blank to keep the stored one, then refresh tools to pick up changes.
Add a declared server
Add without URL opens Register an MCP server:
Name it
How agents and the audit log refer to the server. You can't change it later.
List its tools
Required. One
qualified_tool_name: descriptionper line, using the exact name that appears intool_calls, not a bare tool name. Agents can only be bound to names listed here.Tag it
Comma-separated, for filtering the catalog.
Click Create MCP server to save. Edits to a declared server's tool list or description apply on the next call.
Server detail page
| Tab | What it shows |
|---|---|
| Overview | Tags, description and, for a connected server, its URL, auth type, last check, and how agents connect to the MCP gateway. |
| Tools | Each tool, enabled or disabled. Disabling a tool is a kill switch: no agent can call it from the next attempt, whatever its bindings. |
| Versions | Each version of the tool list, with status and findings. Forgebench checks each version for hidden characters or instructions in descriptions, tools that change data, and changed descriptions or schemas. Findings are advisory; only a changed description or a repointed tool waits for Accept changes. For a declared server, editing the tool list or Publish version adds a version. |
| Agents | Agents bound to a tool on this server. A stale binding names a tool the active version no longer has, and is refused until that tool returns. Revoke N stale removes them. |
| Ceilings | Per-tool call ceilings, added from the Tools tab with Add ceiling. Blank means no ceiling. |
Binding servers to agents
An agent with no binding can call no tool. You can bind from the server or from the agent.
From the server: open its Agents tab, choose Grant to an agent, pick the agent, tick the tools, then Grant tools. Tools that look like they change data start unticked.
From the agent:
Open the agent, then its Tools tab
Choose MCP allowlist. Registry is the separate Tool Registry allowlist.
Suggest tools, or bind manually
Suggest tools ranks the catalog for this agent; only accepting a suggestion creates a binding. Bind tools lets you pick a server and tick its tools. Ticks are kept when you switch servers, so you can bind from several servers at once.
Confirm
The selection becomes live. You can revoke each binding from the MCP allowlist table.
Set a call-volume ceiling
A binding decides whether an agent may call a tool. A ceiling optionally caps how often: a rate limit (per minute or hour) or a quota (per day or month).
Add one with Add ceiling on a tool's row in the server's Tools tab, or
from the Ceilings tab, which lists every ceiling on the server. Leave the
agent field blank to cap all agents combined, or pick one agent. On a declared
server, a call over the ceiling is refused with 429; an unbound tool gets
403.
Agents on a connected server
Agents reach every connected server through one endpoint, /v1/mcp-gateway,
with their own agent key. Point the agent's MCP client at it:
{
"mcpServers": {
"forgebench": {
"type": "http",
"url": "https://api.forgebench.ai/v1/mcp-gateway",
"headers": { "Authorization": "Bearer <agent key>" }
}
}
}- Only an agent's key works. A user's key is refused with
403. tools/listreturns only the tools granted to this agent.tools/callrefuses a paused agent, a revoked server, a disabled tool, a missing binding, and calls over a ceiling or the plan's monthly limit. A tool the agent isn't allowed to call looks the same as one that doesn't exist.- Forgebench adds the server's token to each upstream call and records the outcome.
Tools on declared servers don't appear here; the agent calls those itself.
Handle it in your code (declared servers)
Your code doesn't change: bind the tool, read tool_calls off the response as
usual, and dispatch to your MCP client for names you bound.
The Python and TypeScript SDKs don't have tools or tool_choice parameters,
but Forgebench forwards them. Send them through extra_body in Python, or a
widened type in TypeScript. In Java, use tools(...) on the request builder
and extraBody(...) for tool_choice. In Python, tool_calls is a list of
plain dicts, so index into it (call["function"]["name"]); in Java it is a
List<Map<String, Object>>.
If the model proposes a tool the agent isn't bound to, the whole response is
blocked and raised as PermissionDeniedError with .code set to
mcp_tool_not_authorized:
from forgebench import Forgebench, PermissionDeniedError
client = Forgebench(api_key="sk_...", base_url="https://api.forgebench.ai")
messages = [{"role": "user", "content": "What time is it in Tokyo?"}]
try:
resp = client.chat.completions.create(
model="gpt-4o",
messages=messages,
extra_body={"tools": [...]}, # including mcp_time_get_current_time
)
except PermissionDeniedError as e:
log.error("mcp tool not authorized: %s", e.body)
else:
for call in resp.choices[0].message.tool_calls or []:
name = call["function"]["name"]
if name.startswith("mcp_"):
result = your_mcp_client.call(name, call["function"]["arguments"])import { Forgebench, PermissionDeniedError, type ChatCompletionRequest } from "@seedlinglabs/forgebench-sdk";
const forgebench = new Forgebench({ apiKey: "sk_...", baseUrl: "https://api.forgebench.ai" });
const messages = [{ role: "user" as const, content: "What time is it in Tokyo?" }];
try {
const res = await forgebench.chat.create({
model: "gpt-4o",
messages,
tools: [...], // including mcp_time_get_current_time
} as ChatCompletionRequest & Record<string, unknown>);
for (const call of res.choices[0]?.message.tool_calls ?? []) {
if (call.function.name.startsWith("mcp_")) {
const result = await yourMcpClient.call(call.function.name, call.function.arguments);
}
}
} catch (e) {
if (e instanceof PermissionDeniedError) {
console.error("mcp tool not authorized", e.body);
} else {
throw e;
}
}import ai.forgebench.ChatCompletionRequest;
import ai.forgebench.ChatMessage;
import ai.forgebench.Forgebench;
import ai.forgebench.errors.PermissionDeniedException;
import ai.forgebench.types.ChatCompletion;
import java.util.List;
import java.util.Map;
Forgebench client = Forgebench.builder().apiKey("sk_...").baseUrl("https://api.forgebench.ai").build();
List<ChatMessage> messages = List.of(ChatMessage.user("What time is it in Tokyo?"));
try {
ChatCompletion resp = client.chat().completions().create(ChatCompletionRequest.builder()
.model("gpt-4o")
.messages(messages)
.tools(tools) // including mcp_time_get_current_time
.build());
List<Map<String, Object>> calls = resp.getChoices().get(0).getMessage().getToolCalls();
for (Map<String, Object> call : calls == null ? List.<Map<String, Object>>of() : calls) {
Map<?, ?> fn = (Map<?, ?>) call.get("function");
String name = (String) fn.get("name");
if (name.startsWith("mcp_")) {
Object result = yourMcpClient.call(name, (String) fn.get("arguments"));
}
}
} catch (PermissionDeniedException e) {
log.error("mcp tool not authorized: {}", e.getBody());
}curl -X POST https://api.forgebench.ai/v1/chat/completions \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "What time is it in Tokyo?"}],
"tools": [{
"type": "function",
"function": {
"name": "mcp_time_get_current_time",
"description": "Current time in a timezone",
"parameters": {"type": "object", "properties": {"tz": {"type": "string"}}}
}
}]
}'To fix a 403 mcp_tool_not_authorized, bind the tool or fix the tool schema
you sent. With curl, the refusal is a JSON body:
{"detail":{"code":"mcp_tool_not_authorized","tool":"mcp_time_get_current_time","reason":"not_permitted","message":"this agent may not call that tool"}}Bulk CSV import
To register many declared servers, Import CSV takes columns name,
description, tags and tools. The tools cell holds one
qualified_tool_name: description per line, quoted as standard CSV. Rows with
a duplicate name or no tool list are skipped; the rest import.
Revoke vs delete
| Action | What happens |
|---|---|
| Revoke | Every bound agent loses access on its next attempt. Bindings are marked revoked, not deleted, and the server stays in the catalog marked revoked. |
| Delete | Only a revoked server can be deleted. This permanently removes it and its bindings. The audit log still records it. There is no undo. |
To revoke, open the ⋯ menu on the server's row, choose Revoke, and confirm. A revoked server's menu offers only Delete, which asks for its own confirmation.
MCP vs Tool Registry
- MCP governs MCP servers. Forgebench calls a connected server's tools for the agent, or authorizes the agent to call a declared server itself.
- Tool Registry governs tools with no server, which your own code runs after the call returns.
A tool call whose name starts with mcp_ is checked only against the MCP
allowlist; every other name is checked only against the Tool Registry. Set up
a tool in the wrong one and neither authorizes it.

