Skip to content

Add a resources_list_handler for context-dependent resource lists - #509

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:add_resources_list_handler
Open

Add a resources_list_handler for context-dependent resource lists#509
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:add_resources_list_handler

Conversation

@koic

@koic koic commented Aug 12, 2026

Copy link
Copy Markdown
Member

Motivation and Context

resources/read can be taken over with resources_read_handler, but resources/list had no equivalent: it was bound to the internal list_resources, which paginates the constructor-provided resources array, and define_custom_method refuses to rebind a method the server already handles. An application whose visible resources depend on the request - authenticated versus anonymous sessions, or an OAuth-scoped connector that must see a scope-filtered set - had to build a fresh Server per request or override a private method.

Both reference SDKs let the list vary per request. The TypeScript SDK exposes resources/list through setRequestHandler and gives resource templates a per-context listCallback(ctx); the Python SDK takes an on_list_resources handler. This adds the Ruby equivalent.

resources_list_handler(&block) registers a block that returns the resource collection to serve. It feeds list_resources rather than replacing it, so the framework still paginates the returned array and stamps the SEP-2549 cache hints; the block returns only the array, matching how resources_read_handler returns only the contents and the framework wraps them. A block that declares a server_context: keyword receives an MCP::ServerContext, reusing the same opt-in rule the read handler uses, so the list can be filtered by the authenticated principal or granted scope. The block is invoked once per page and must return a stable ordering across the pages of one query, since the cursor is a positional offset. When no handler is set, the constructor-provided resources are served unchanged.

The resources capability is advertised whether or not the constructor array is populated, so a server that provides only a resources_list_handler still answers resources/list.

Fixes #507.

How Has This Been Tested?

New tests in test/mcp/server_test.rb cover a handler replacing the served collection, a handler receiving server_context: and returning different sets for authenticated and anonymous requests, pagination and cache hints still applying to the handler-provided array, and a server with no constructor-provided resources served entirely by the handler. The existing resources/list test, which exercises the no-handler default path, is unchanged.

Breaking Changes

None. When no resources_list_handler is set, resources/list serves the constructor-provided resources exactly as before.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

`resources/read` can be taken over with `resources_read_handler`, but `resources/list` had no equivalent:
it was bound to the internal `list_resources`, which paginates the constructor-provided `resources` array,
and `define_custom_method` refuses to rebind a method the server already handles.
An application whose visible resources depend on the request - authenticated versus anonymous sessions,
or an OAuth-scoped connector that must see a scope-filtered set - had to build a fresh `Server` per request
or override a private method.

Both reference SDKs let the list vary per request. The TypeScript SDK exposes `resources/list` through
`setRequestHandler` and gives resource templates a per-context `listCallback(ctx)`; the Python SDK takes
an `on_list_resources` handler. This adds the Ruby equivalent.

`resources_list_handler(&block)` registers a block that returns the resource collection to serve.
It feeds `list_resources` rather than replacing it, so the framework still paginates the returned array
and stamps the SEP-2549 cache hints; the block returns only the array, matching how `resources_read_handler` returns
only the contents and the framework wraps them. A block that declares a `server_context:` keyword receives
an `MCP::ServerContext`, reusing the same opt-in rule the read handler uses, so the list can be filtered by
the authenticated principal or granted scope. The block is invoked once per page and must return
a stable ordering across the pages of one query, since the cursor is a positional offset. When no handler is set,
the constructor-provided resources are served unchanged.

The `resources` capability is advertised whether or not the constructor array is populated, so a server that
provides only a `resources_list_handler` still answers `resources/list`.

Fixes modelcontextprotocol#507.

## How Has This Been Tested?

New tests in `test/mcp/server_test.rb` cover a handler replacing the served collection, a handler receiving `server_context:`
and returning different sets for authenticated and anonymous requests, pagination and cache hints still applying to
the handler-provided array, and a server with no constructor-provided resources served entirely by the handler.
The existing `resources/list` test, which exercises the no-handler default path, is unchanged.

## Breaking Changes

None. When no `resources_list_handler` is set, `resources/list` serves the constructor-provided resources exactly as before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No customization hook for resources/list (unlike resources/read's resources_read_handler)

1 participant