App MCP Tools
What It Is
An app can offer some of its routes as tools to MCP clients — Claude, ChatGPT, IDE assistants — and to the tenant's own AI agents. A tool is a route with a description a model can read and a JSON Schema of its arguments; it appears next to the tenant's built-in tools.
"routes": {
"GET /cases/search": { "handler": "server/cases/search.php", "auth": "role:case_reader,case_writer" }
},
"mcp": {
"tools": [{
"name": "find_case",
"title": "Find a case",
"description": "Find cases by client name or reference. Use it before opening a new case.",
"route": "GET /cases/search",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string", "minLength": 2 } },
"required": ["query"],
"additionalProperties": false
},
"annotations": { "readOnlyHint": true }
}]
}
Why It Matters
The built-in tools let a model read and write records generically. An app tool carries the app's own knowledge — how to find a case, what "escalate" means, which checks come first — in one operation the model can call safely, with the same rules as the app's screens.
How It Works
- Who sees a tool. A tool is listed only to callers who may open the app and call its route: a
role:route shows its tool to those roles only. Superusers see every tool. - Who it runs as. The route runs as the caller, with the caller's permissions, exactly as when the app's front-end calls it. Tools cannot use
internalroutes. - Arguments. They are checked against
inputSchemabefore the route runs; a call that does not match fails with the problems listed. The handler receives them as its JSON body ($body), scalar ones also as$query, path parameters of the route as$params, and the call as$tool(name,arguments). - The result. The handler's JSON becomes the tool result (
structuredContent; a list is wrapped as{"result": […]}). An answer of 400 or more, or a handler error, is a failed call with the message or error id. - Names. On the wire a tool is
<tenant prefix>_<app>_<tool>, e.g.acme_case_portal_find_case, shortened with a hash beyond 64 characters. A deploy is refused when a name is taken — by a built-in tool or another app's tool.
Writing Good Tools
- Describe when to use the tool, not only what it does — models choose tools by their descriptions.
- Keep arguments few and constrained:
enum,minLength,additionalProperties: false. - Return compact JSON with the fields a model needs to continue; link records by id.
- Mark read-only tools with
"readOnlyHint": true, and tools that delete or send with"destructiveHint": true.
Tool Options
| Key | Meaning |
|---|---|
name | Lowercase letters, digits and underscores (at most 40), unique within the app. |
title | A short human-readable name. |
description | What the tool does and when to use it (at most 2000 characters). |
route | A route of the app that is not internal. Its path parameters must be required arguments. |
inputSchema | A JSON Schema of type object for the arguments. |
outputSchema | Optional JSON Schema of the result, for clients that use it. |
annotations | Hints for clients: readOnlyHint, destructiveHint, idempotentHint, openWorldHint. |
Tenant Settings
These keys are not on the Application settings screen; set them in the tenant configuration.
customAppsMcpTools
What it controls:
- whether apps'
mcp.toolsare offered by the tenant's MCP server and to its AI agents
Default:
true
How to configure it:
- set it to
falseto keep MCP clients and agents to the built-in tools
What it affects:
- app tools are neither listed nor callable while it is off; the app's routes still work for its front-end
- turning the whole MCP server off is
mcpServer