Skip to content

MCP Saved-Query Tools

Saved-query tools are JSON-defined, parameterized queries scoped to a collection. They appear as first-class MCP tools alongside core tools like query_collection — AI agents pick them by name, pass arguments, and receive filtered, formatted results. No PHP is needed; the definition lives in the collection’s schema editor.

Use saved-query tools when a particular query is reused or when you want to give an AI agent a stable, named entry point. For ad-hoc filter composition, query_collection is the better choice.

Open the schema editor for the collection you want to expose. On the MCP tab, find the Custom MCP Tools field — it renders as a deck of tool rows. Click Add and fill in the id, description, filters, etc. Behind the scenes the deck stores tools keyed by their id:

{
"featured_posts": {
"id": "featured_posts",
"description": "Return featured blog posts, newest first.",
"filters": {
"featured": { "value": true }
},
"sort": "date:desc",
"limit": 10
}
}

The deck key matches the tool’s id — that’s the canonical identifier; the inner id field is what you enter in the form. Save the schema. The tool featured_posts is live on /mcp immediately — no rebuild, no restart.

Each tool entry is an object with the following fields. Only id and description are required.

FieldRequiredTypeNotes
idyesstringSnake_case ^[a-z][a-z0-9_]*$. Becomes the wire-level tool name when registered. The registered name (base + any mcp.toolPrefix) must be ≤ 64 characters; the save-time validator enforces the full limit. Globally unique across all tools.
descriptionyesstringMin 1, max 1024 characters. Describe what the tool returns — not how an agent should use it.
paramsnoobjectTyped caller parameters (see Parameterized tools). Omit for fixed-filter tools.
filtersnoobjectField-name → {value, operator?} map. Filter values may reference {{params.X}} placeholders.
sortnostringREST-style sort spec, e.g. date:desc or priority:asc,date:desc.
limitnointegerMax results per call. Default 20, capped at 50.
offsetnointegerSkip the first N matching results. Default 0.
includenostringREST-style include filter — escape hatch for filter shapes the filters object can’t express.
excludenostringREST-style exclude filter — escape hatch.
formatnoenumOutput format for rich-text fields: markdown (default), html, or text.

The mcp-tool.json JSON Schema enforces maxLength: 64 on the base id field only — it cannot dynamically account for the site’s configured mcp.toolPrefix. The save-time validator performs the full prefix-inclusive check: strlen(toolPrefix + "_" + id) ≤ 64. If a site has a 10-character prefix configured, base ids must be ≤ 54 characters. The error message will state both the prefix in use and the resulting length.

Fixed-filter tools take no caller arguments. They are preset queries with a stable result shape — useful for “give me all draft posts” or “return the three most recent announcements”:

{
"draft_posts": {
"id": "draft_posts",
"description": "Return blog posts pending editorial review, oldest first.",
"filters": {
"status": { "value": "draft" }
},
"sort": "date:asc",
"limit": 20
},
"recent_announcements": {
"id": "recent_announcements",
"description": "The three most recent site announcements.",
"sort": "date:desc",
"limit": 3
}
}

Add a params block to accept caller-supplied arguments. Reference them in filter values with {{params.X}} placeholders.

{
"id": "find_listings_by_city",
"description": "Search active real-estate listings by city and optional maximum price.",
"params": {
"city": {
"type": "string",
"description": "City name (case-insensitive substring match).",
"required": true
},
"max_price": {
"type": "number",
"description": "Maximum listing price in USD.",
"minimum": 0
}
},
"filters": {
"status": { "value": "active" },
"city": { "operator": "contains", "value": "{{params.city}}" },
"price": { "operator": "lte", "value": "{{params.max_price}}" }
},
"sort": "price:asc",
"limit": 20
}
  • {{params.X}} is the only recognized placeholder syntax. Other {{...}} strings in filter values are treated as literals.
  • When a placeholder replaces the entire filter value (e.g. "value": "{{params.city}}"), the resolved value is type-coerced to match the param’s declared type.
  • When a placeholder is embedded in a longer string (e.g. "value": "category-{{params.slug}}"), the resolved segment is cast to string and the parts are concatenated.
  • If an optional param is not supplied by the caller, the filter containing its placeholder is excluded from the query. The tool still runs against the remaining filters.
PropertyRequiredTypeNotes
typeyesenumstring, number, integer, boolean
descriptionnostringShown to AI agents in the generated inputSchema
requirednobooleanDefault false. Required params are enforced by the SDK before dispatch.
defaultnoanyDefault value used when the param is absent
enumnoarrayRestrict to a specific set of values
minimumnonumberNumeric lower bound
maximumnonumberNumeric upper bound
formatnostringJSON Schema format hint (e.g. date, uri)

Param names must match ^[a-z][a-z0-9_]*$.

Specify an operator alongside value in any filter entry. The default is eq.

OperatorMeaning
eqEqual (default)
neNot equal
ltLess than
lteLess than or equal
gtGreater than
gteGreater than or equal
containsSubstring match (case-insensitive for string fields)
startsStarts with
endsEnds with
inValue is in a pipe-separated list (a|b|c)
notinValue is not in a pipe-separated list
"filters": {
"tag": { "operator": "contains", "value": "featured" },
"rating": { "operator": "gte", "value": 4 },
"status": { "operator": "notin", "value": "draft|archived" }
}

A saved-query tool inherits its access level from the parent collection’s MCP Access setting (admin or public). There is no per-tool override.

When the public persona calls the tool, T3’s standard safety filters run after the tool’s filters: draft objects are excluded, and fields marked expose: false in the collection’s MCP field settings are stripped from every response. Saved-query tools cannot bypass these filters.

Collision detection runs at two points:

Save-time (warning, non-blocking). When you save the schema, each tool name is checked against currently registered core and extension tools. A collision triggers a warning banner in the admin — it does not block the save. The colliding tool will be silently skipped from tools/list at runtime until the name is changed.

Server-build-time (strict deny). When the MCP server initializes, SchemaToolRegistrar walks all collections. If the same tool name appears in two different collections, both tools are dropped from tools/list. Core and extension tools always win over schema tools. Rename one of the conflicting tools to resolve the issue.

If a tool is missing from tools/list despite being defined in the schema, check the admin’s warnings UI for collision notices and review the application log for entries from SchemaToolRegistrar.

Saved-query toolquery_collection
DefinitionJSON in schema editorCalled ad-hoc by AI agent
Agent selects byTool nameTool name + inline args
Best forStable, reused queriesAd-hoc filter composition
Caller can change filtersNo (fixed + declared params only)Yes
Requires PHPNoNo

Use a saved-query tool when you want a predictable, named entry point. Use query_collection when the agent needs to compose filters freely.

Tool id format. Only lowercase letters, digits, and underscores. Must start with a letter. Max 64 characters including any mcp.toolPrefix the operator has configured.

Total tool count. Most MCP clients degrade in usability above roughly 50 tools. If you define many saved-query tools across multiple collections, watch the cumulative count in the admin’s MCP status panel.

{{params.X}} is the only placeholder syntax. Filter values containing other {{...}} strings are passed through as literals. Future macro syntax will use a different prefix.

Referencing an undeclared param. If a filter value contains {{params.X}} but X is not declared in the params block, the tool fails at runtime with a validation error. The admin’s warnings UI will surface the issue on next save.

Do not write LLM instructions into the description. “Always call this tool first” reads as prompt injection at directory review. Lead with what the tool returns.

  • MCP Server — personas, transport, core tool catalog, collection MCP settings.
  • Extending MCP — registering tools and resources from PHP extensions, progress notifications, error handling, persona-aware handlers.