F2: Tool Discovery API¶
| Field | Value |
|---|---|
| Feature ID | F2 |
| Title | Tool Discovery API |
| Complexity | S |
| Priority | P0 |
| Dependencies | None |
| Status | Draft |
Overview¶
Two read-only HTTP endpoints that expose tool definitions to the embedded
frontend. The endpoints are backed by a ToolsProvider abstraction that
supports static lists, sync callables, and async callables across all
target languages.
Server-level metadata (title, allow_execute) is injected into the HTML
via template variables ({{TITLE}}, {{ALLOW_EXECUTE}}) at render time,
eliminating the need for a separate metadata endpoint.
Endpoints¶
GET /tools¶
Returns a JSON array of tool summaries. If the ToolsProvider is a
callable, it is invoked on every request (no caching -- caller's
responsibility).
Response 200 OK application/json
[
{
"name": "get_weather",
"description": "Fetch current weather for a city.",
"annotations": {
"readOnlyHint": true,
"openWorldHint": true
}
},
{
"name": "delete_record",
"description": "Delete a record by ID."
}
]
| Field | Type | Description |
|---|---|---|
name |
string | Tool identifier |
description |
string | Human-readable description |
annotations |
object | omitted | MCP annotations; omitted entirely when the tool has none (never null) |
Annotations fields (all optional booleans):
readOnlyHint, destructiveHint, idempotentHint, openWorldHint.
GET /tools/{name}¶
Returns a single tool with full detail, including its input schema.
Response 200 OK application/json
{
"name": "get_weather",
"description": "Fetch current weather for a city.",
"annotations": {
"readOnlyHint": true,
"openWorldHint": true
},
"inputSchema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}
| Field | Type | Description |
|---|---|---|
name |
string | Tool identifier |
description |
string | Human-readable description |
annotations |
object | omitted | MCP annotations; omitted when none |
inputSchema |
object | JSON Schema describing the tool's input parameters |
Error 404 Not Found
ToolsProvider Abstraction¶
The ToolsProvider is the single source of tool definitions. It accepts
three shapes:
| Shape | Description |
|---|---|
| Static list | A plain list/array of Tool objects |
| Sync callable | A zero-argument function returning Tool[] |
| Async callable | A zero-argument async function returning Tool[] |
Resolution Algorithm¶
1. If provider is a list -> use it directly.
2. Otherwise -> call it.
3. If the result is awaitable -> await it.
4. Use the resolved list.
When the provider is callable it is invoked on every request. The library performs no internal caching; callers who need caching must implement it in their callable.
Cross-Language Mapping¶
| Language | ToolsProvider type |
|---|---|
| Python | list \| Callable \| AsyncCallable |
| TypeScript | Tool[] \| (() => Tool[]) \| (() => Promise<Tool[]>) |
| Go | []Tool or ToolProvider interface { Tools() []Tool } |
| Rust | Vec<Arc<dyn Tool>> or impl ToolsProvider trait |
Tool Object Protocol¶
Tools are duck-typed. Any object exposing the following attributes is accepted:
| Attribute | Type | Description |
|---|---|---|
name |
string | Tool identifier |
description |
string | Human-readable description |
inputSchema |
dict / object | JSON Schema for input parameters |
annotations |
object | null | Optional MCP annotations (see above) |
Serialization Rules¶
Tool objects are serialized to plain dicts for JSON output. The following strategies are tried in order:
- Pydantic v2 -- object has
.model_dump()-> callmodel_dump(exclude_none=True). - Pydantic v1 -- object has
.dict()-> calldict(exclude_none=True). - Plain dict -- recurse into values (apply the same rules to nested objects).
- Everything else -- pass through as-is.
The exclude_none=True parameter ensures that annotations (and any
other optional field set to None) is omitted from the output rather
than serialized as null.
Test Criteria¶
- [ ]
GET /toolsreturns all tools with correct fields. - [ ]
GET /toolsreturnsannotationsonly when present (key is omitted, notnull). - [ ]
GET /tools/{name}returnsinputSchemain the response. - [ ]
GET /tools/{unknown}returns404with an error message. - [ ] Sync callable provider is invoked on each request (no caching).
- [ ] Async callable provider resolves correctly.