You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
resources/list can only be served from a static, in-memory set. Resources are materialized up front via AddResource (removed via RemoveResources), and (*Server).listResources enumerates that featureSet directly — there is no handler/callback that computes the list on demand. For two important classes of server this isn't just inconvenient, it's a structural mismatch.
1. Gateway / proxy servers (the important case)
A skill (or resource) gateway fronts one or more upstream catalogs it does not own — another registry, a remote index, or several backend MCP servers. The authoritative list lives elsewhere and changes independently of this process. To serve resources/list from a static set, the gateway would have to mirror the entire upstream catalog into memory at startup and keep it continuously in sync — which defeats the purpose of being a gateway, doesn't scale to large/unbounded upstreams, and is racy against upstream changes.
What a gateway actually needs is to answer resources/list by querying upstream on demand, passing the client's pagination cursor through. There is no first-class way to do that today.
2. Template-served assets are non-enumerable by construction
Skills are served as a directory of files. A server typically exposes a skill's files via a resource template (skill://<skill>/{+path}) plus one read handler, so it doesn't have to AddResource every individual file. But a URI template matches infinitely many URIs — you cannot derive the file list from the template definition. So those assets are fully readable yet absent from any static set: resources/list won't show them, and resources/directory/read (below) backed by the static set returns empty.
Relationship to resources/directory/read (#956 / SEP-2640)
The Skills-over-MCP WG is adding a resources/directory/read method (SEP-2640) so hosts can enumerate the files within a skill directory — e.g. to materialize a skill to disk. Implementing that verb in this SDK depends on custom-method support (#956). But even once the verb exists, it hits exactly this problem: for a template-served or gateway-proxied skill, the directory's children cannot come from the static set — resources/directory/read must be backed by a server-provided dynamic listing.
this issue — gives resources/list / directory-read a dynamic backing so template-served and gateway-proxied entries can actually be enumerated.
Neither alone lets a Go skills/gateway server both serve and enumerate a catalog whose contents aren't pre-materialized.
What already works (to scope precisely)
resources/read is already dynamic — content comes from a ResourceHandler, and resource templates (AddResourceTemplate + ResourceTemplate.Matches) let one handler serve an unbounded URI space. The asymmetry is the whole point: templates make reads unbounded, but nothing makes listing/enumeration unbounded or on-demand.
Workarounds today (and why they fall short)
Receiving middleware.Middleware wraps MethodHandler func(ctx, method string, req Request)(Result, error), so middleware can intercept resources/list and synthesize a *ListResourcesResult, short-circuiting the static handler. It works, but it's untyped (switch on a string method, type-assert req/Result), bypasses the SDK's built-in pagination, and sits awkwardly beside the otherwise-typed registration model.
Per-session server via the getServer callback (as suggested in Support for per session dynamic tools/prompts #216). Good for per-connection configuration, but it still requires materializing a concrete set per server — it doesn't help a catalog that is upstream, generated, or too large to enumerate eagerly.
Proposal
Add a first-class way to serve resources/list (and back resources/directory/read) from a handler, with cursor-based pagination handled by the SDK — analogous to how ResourceHandler/templates already make reads dynamic. Illustrative shape (not prescriptive):
// Serves resources/list on demand; receives the request (incl. pagination cursor)// and returns a page of resources plus the next cursor.typeListResourcesHandlerfunc(context.Context, *ListResourcesRequest) (*ListResourcesResult, error)
opts:=&mcp.ServerOptions{
ListResourcesHandler: func(ctx context.Context, req*mcp.ListResourcesRequest) (*mcp.ListResourcesResult, error) {
// e.g. proxy upstream using req.Params.Cursor, or enumerate a template's namespace ...
},
}
When set, the handler would take precedence over (or compose with) the registered static set, and the same mechanism would back directory enumeration for a directory URI.
Questions for maintainers
Was the static-set model for resources/list a deliberate choice (uniformity with tools/prompts feature sets), and would a dynamic list handler be acceptable — or is middleware / per-session-server the intended long-term answer for gateway and unbounded catalogs?
If a handler is acceptable: should it replace or merge with the registered set, and should the same backing apply to resources/directory/read (mcp: Allow registration of custom JSON-RPC methods #956) and, for consistency, prompts/list / tools/list?
Happy to prototype if there's interest in the direction.
Problem
resources/listcan only be served from a static, in-memory set. Resources are materialized up front viaAddResource(removed viaRemoveResources), and(*Server).listResourcesenumerates thatfeatureSetdirectly — there is no handler/callback that computes the list on demand. For two important classes of server this isn't just inconvenient, it's a structural mismatch.1. Gateway / proxy servers (the important case)
A skill (or resource) gateway fronts one or more upstream catalogs it does not own — another registry, a remote index, or several backend MCP servers. The authoritative list lives elsewhere and changes independently of this process. To serve
resources/listfrom a static set, the gateway would have to mirror the entire upstream catalog into memory at startup and keep it continuously in sync — which defeats the purpose of being a gateway, doesn't scale to large/unbounded upstreams, and is racy against upstream changes.What a gateway actually needs is to answer
resources/listby querying upstream on demand, passing the client's pagination cursor through. There is no first-class way to do that today.2. Template-served assets are non-enumerable by construction
Skills are served as a directory of files. A server typically exposes a skill's files via a resource template (
skill://<skill>/{+path}) plus one read handler, so it doesn't have toAddResourceevery individual file. But a URI template matches infinitely many URIs — you cannot derive the file list from the template definition. So those assets are fully readable yet absent from any static set:resources/listwon't show them, andresources/directory/read(below) backed by the static set returns empty.Relationship to
resources/directory/read(#956 / SEP-2640)The Skills-over-MCP WG is adding a
resources/directory/readmethod (SEP-2640) so hosts can enumerate the files within a skill directory — e.g. to materialize a skill to disk. Implementing that verb in this SDK depends on custom-method support (#956). But even once the verb exists, it hits exactly this problem: for a template-served or gateway-proxied skill, the directory's children cannot come from the static set —resources/directory/readmust be backed by a server-provided dynamic listing.So the two are a pair:
resources/directory/readverb (custom method).resources/list/ directory-read a dynamic backing so template-served and gateway-proxied entries can actually be enumerated.Neither alone lets a Go skills/gateway server both serve and enumerate a catalog whose contents aren't pre-materialized.
What already works (to scope precisely)
resources/readis already dynamic — content comes from aResourceHandler, and resource templates (AddResourceTemplate+ResourceTemplate.Matches) let one handler serve an unbounded URI space. The asymmetry is the whole point: templates make reads unbounded, but nothing makes listing/enumeration unbounded or on-demand.Workarounds today (and why they fall short)
MiddlewarewrapsMethodHandler func(ctx, method string, req Request)(Result, error), so middleware can interceptresources/listand synthesize a*ListResourcesResult, short-circuiting the static handler. It works, but it's untyped (switch on a string method, type-assertreq/Result), bypasses the SDK's built-in pagination, and sits awkwardly beside the otherwise-typed registration model.getServercallback (as suggested in Support for per session dynamic tools/prompts #216). Good for per-connection configuration, but it still requires materializing a concrete set per server — it doesn't help a catalog that is upstream, generated, or too large to enumerate eagerly.Proposal
Add a first-class way to serve
resources/list(and backresources/directory/read) from a handler, with cursor-based pagination handled by the SDK — analogous to howResourceHandler/templates already make reads dynamic. Illustrative shape (not prescriptive):When set, the handler would take precedence over (or compose with) the registered static set, and the same mechanism would back directory enumeration for a directory URI.
Questions for maintainers
resources/lista deliberate choice (uniformity with tools/prompts feature sets), and would a dynamic list handler be acceptable — or is middleware / per-session-server the intended long-term answer for gateway and unbounded catalogs?resources/directory/read(mcp: Allow registration of custom JSON-RPC methods #956) and, for consistency,prompts/list/tools/list?Happy to prototype if there's interest in the direction.