modelcontextprotocol / modelcontextprotocol/go-sdk

Proposal: dynamic/lazy resources/list (and directory-read) backing for gateway/proxy and template-served catalogs

Open
#998 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

P3
Dominant language
Go
Stars
5.1k
Forks
543
Avg merge
1d 17h
Merged PRs (30d)
37

Description

Problem

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.

So the two are a pair:

  • #956 — adds the resources/directory/read verb (custom method).
  • 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)

  1. 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.
  2. Per-session server via the getServer callback (as suggested in #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.
type ListResourcesHandler func(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 (#956) and, for consistency, prompts/list / tools/list?

Happy to prototype if there's interest in the direction.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with (*Server).listResources and the ServerOptions registration path, then compare the existing ResourceHandler, AddResourceTemplate, and pagination behavior. Review the Middleware workaround and the relationship to custom-method support for resources/directory/read; done means the agreed dynamic listing design is implemented with clear precedence or composition semantics.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.