Skip to content

Host Resources

Capability ai.nimblebrain/host-resources, in the client’s capabilities.extensions
Methods ai.nimblebrain/resources/read, ai.nimblebrain/resources/list, sent by the server to the host
Eras 2025-era connections only. Not claimed or served on 2026-07-28; see Which connections can use it
Schemas The specification’s ReadResourceRequest/Result and ListResourcesRequest/Result, unchanged
Fallback The tool takes the content inline as an argument

MCP resources flow one way: a server exposes them and a client reads them. Nothing in the specification lets a server read something that belongs to the client.

On NimbleBrain, that gap shows up constantly. A user drops a contract, a spreadsheet, or a set of images into the workspace and asks the agent to hand it to a server’s tool. Without this extension, the model’s only option is to read the file and paste its contents into a tool argument. That path spends tokens on every byte twice, is capped by the context window, corrupts binary data unless the model base64-encodes it correctly, and puts the model in the middle of a copy it has no reason to see.

With this extension, the tool takes a URI such as files://fl_abc123, and the server reads the file from the host itself. The model passes a short reference and never sees the bytes.

The methods are served on 2025-era connections only: a connection that negotiated 2025-11-25 or an earlier revision through initialize.

They are requests the server sends to the client. The 2026-07-28 revision removed server-to-client requests: a server that needs something from the client mid-request answers with input_required and a closed set of input requests (elicitation, sampling, roots), and that set has no place for an extension’s method yet. Adding one is proposed upstream in modelcontextprotocol#2919. Until the specification has such a hook, a server that negotiated 2026-07-28 with the host cannot reach these methods, and its tools should take content inline.

The host claims the capability only where it serves the methods, so checking for the capability is enough. On a 2025-era connection it is in the initialize request. On a 2026-07-28 connection it is absent from every request’s _meta["io.modelcontextprotocol/clientCapabilities"]. A server that also wants to be explicit can check the negotiated protocol version: the methods are reachable only when it is earlier than 2026-07-28.

On a 2025-era connection, the host advertises the capability in initialize:

{
"capabilities": {
"extensions": {
"ai.nimblebrain/host-resources": {
"read": { "enabled": true, "range": false, "maxSize": 10485760 },
"list": { "enabled": true },
"write": { "enabled": false },
"schemes": ["files"]
}
}
}
}
Field Meaning
read.enabled ai.nimblebrain/resources/read is served.
read.range Reads of a byte range are supported. false today: every read returns the whole resource.
read.maxSize The largest resource, in bytes, a read will return (10 MiB). A larger one fails with -32005.
list.enabled ai.nimblebrain/resources/list is served.
write.enabled Writing back to the host. false today.
schemes The URI schemes the host resolves. A URI outside this list fails with -32602.

Each operation is an object rather than a boolean so that new options (range reads, filters) can be added without breaking a server that reads the current shape. Check enabled on the specific operation you call. Do not infer one operation from another.

Read one resource. The params and result are exactly those of the specification’s resources/read.

// Server → Host
{
"jsonrpc": "2.0",
"id": 7,
"method": "ai.nimblebrain/resources/read",
"params": { "uri": "files://fl_abc123" }
}
// Host → Server
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"contents": [
{ "uri": "files://fl_abc123", "mimeType": "text/csv", "text": "region,revenue\n..." }
]
}
}

A text MIME type comes back as text. Anything else comes back base64-encoded in blob.

List the resources the server can read. The params and result are those of the specification’s resources/list. An optional filter rides in params._meta.filter, because the specification’s list request has no filter field:

// Server → Host
{
"jsonrpc": "2.0",
"id": 8,
"method": "ai.nimblebrain/resources/list",
"params": {
"_meta": { "filter": { "mimeType": "application/pdf", "tags": ["contracts"] } }
}
}
// Host → Server
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"resources": [
{ "uri": "files://fl_def456", "name": "msa-2026.pdf", "mimeType": "application/pdf" }
]
}
}
Filter field Matches
scheme Only files is accepted. Any other value fails with -32602.
mimeType An exact MIME type.
tags Files carrying every listed tag. Must be an array.

The whole list comes back in one response. Pagination is not supported, and a request that sends a cursor fails with -32602. It fails instead of silently returning the full set, so a server’s pagination loop knows the feature is missing.

A request resolves in the workspace the calling connection belongs to, for the user on whose behalf the tool is running. The URI names a file, never a workspace. The host takes the workspace from the connection, so a server cannot reach another workspace’s files by constructing a URI.

A file that does not exist, and a file that exists somewhere the caller cannot see, return the same -32002. A server cannot use reads to discover what exists outside its scope.

The error codes are the specification’s where one exists, so a future upstream version of this extension keeps them.

Code Meaning error.data
-32002 Resource not found, or not visible to this caller { uri }
-32602 Invalid params: unsupported scheme, a cursor, or a malformed filter Scheme: { uri | scheme, supported }. Cursor: { cursor }. Non-array tags: { receivedType }
-32004 Rate limited. Each server in each workspace has its own request budget. { retryAfterMs }
-32005 Response too large: the resource exceeds read.maxSize { uri, size, maxSize }

Design the tool so it works on any host. Accept a URI, accept inline content as the fallback, and let the capability decide which one is used.

The nimblebrain-bundle-sdk package wraps the capability check, the method names, and the result types for FastMCP servers.

from fastmcp import Context
from nimblebrain_bundle_sdk import host
@mcp.tool
async def summarize(file_uri: str | None = None, text: str | None = None, ctx: Context = None) -> dict:
"""Summarize a document. Pass file_uri for a workspace file, or text inline."""
h = host(ctx)
if file_uri and h.available:
result = await h.read(file_uri)
content = result.contents[0]
if getattr(content, "text", None) is None:
return {"error": "summarize reads text files. This file came back as a binary blob."}
text = content.text
elif file_uri:
return {"error": "This host cannot read files by URI. Pass the content as `text`."}
...

h.available is true when read is enabled, and h.list_available is true when list is enabled. h.supports_scheme("files") checks the scheme allowlist.