Skip to content

feat(mcp): expose list_resources, list_resource_templates, and read_resource on MCPServer#2721

Open
adityasingh2400 wants to merge 9 commits intoopenai:mainfrom
adityasingh2400:feat/mcp-list-read-resources
Open

feat(mcp): expose list_resources, list_resource_templates, and read_resource on MCPServer#2721
adityasingh2400 wants to merge 9 commits intoopenai:mainfrom
adityasingh2400:feat/mcp-list-read-resources

Conversation

@adityasingh2400
Copy link
Contributor

Problem

The MCP specification defines a Resources primitive alongside Tools and Prompts. The underlying ClientSession from the MCP Python SDK already implements list_resources(), list_resource_templates(), and read_resource(uri), but none of these are surfaced through MCPServer or its concrete implementations.

This means callers cannot retrieve file contents, database records, or any other server-managed resources even though the transport layer fully supports it.

Solution

Add the three resource methods in three places, following the exact same pattern as the existing list_prompts / get_prompt pair:

  1. Abstract methods on MCPServer — establishing the contract for all server types
  2. Concrete implementations on _MCPServerWithClientSession — delegating to session.list_resources(), session.list_resource_templates(), and session.read_resource(uri) via _maybe_serialize_request (so serialization and retry logic apply automatically)
  3. Stub implementations in test helpers — so FakeMCPServer and the other test-only subclasses remain concrete

Tests

Six new tests in tests/mcp/test_mcp_resources.py:

  • test_list_resources_raises_when_not_connected
  • test_list_resource_templates_raises_when_not_connected
  • test_read_resource_raises_when_not_connected
  • test_list_resources_returns_result
  • test_list_resource_templates_returns_result
  • test_read_resource_returns_result

Usage

async with MCPServerStreamableHttp(params={"url": "http://localhost:8000/mcp"}) as server:
    # List available resources
    resources = await server.list_resources()
    for resource in resources.resources:
        print(resource.uri, resource.name)

    # Read a specific resource by URI
    result = await server.read_resource("file:///path/to/data.csv")
    for content in result.contents:
        print(content.text)  # TextResourceContents

    # List URI templates the server supports
    templates = await server.list_resource_templates()

…esource

The MCP specification defines a Resources primitive alongside Tools and
Prompts, and the underlying ClientSession already implements
list_resources(), list_resource_templates(), and read_resource(), but
none of these were surfaced through MCPServer or its concrete
implementations.

This adds them in three places:
- Abstract methods on MCPServer (parallel to list_prompts/get_prompt)
- Concrete implementations on _MCPServerWithClientSession, delegating
  to session.list_resources() / .list_resource_templates() /
  .read_resource(uri) via _maybe_serialize_request
- Stub implementations in all test helpers that subclass MCPServer

Six tests cover the new surface:
- Raises UserError when not connected (x3)
- Delegates to the underlying session and returns its result (x3)

Closes: (new feature — no existing issue)
@github-actions github-actions bot added enhancement New feature or request feature:mcp labels Mar 19, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c59c740b23

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…bclasses

Per Codex P1 review: adding abstract methods to the public MCPServer base
class would break any existing user-defined subclass that only implements
the previously required methods.

Changed list_resources, list_resource_templates, and read_resource to
concrete methods with a descriptive NotImplementedError default.
Existing subclasses continue to instantiate; they only raise at call time
if the user actually invokes a resource method they haven't implemented.

Also adds a regression test verifying the NotImplementedError path.
@adityasingh2400
Copy link
Contributor Author

Fixed the P1 Codex issue — list_resources, list_resource_templates, and read_resource are now non-abstract with a descriptive NotImplementedError default on the base class. Existing subclasses continue to instantiate normally and only raise if they actually call a resource method they haven't implemented. Added a regression test covering this path.

@seratch seratch added this to the 0.12.x milestone Mar 19, 2026
@seratch seratch enabled auto-merge (squash) March 19, 2026 23:35
…types

mypy requires AnyUrl (not str) for:
- ClientSession.read_resource(uri) argument
- Resource.uri and TextResourceContents.uri fields

Convert str -> AnyUrl at the call site in _MCPServerWithClientSession
so the public API stays str (ergonomic) while satisfying the type checker.
auto-merge was automatically disabled March 19, 2026 23:43

Head branch was pushed to by a user without write access

mcp.types re-exports AnyUrl at runtime but mypy reports it as not
explicitly exported ([attr-defined]). Import directly from pydantic
where AnyUrl originates.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fbb7efc06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

raise UserError("Server not initialized. Make sure you call `connect()` first.")
session = self.session
assert session is not None
return await self._maybe_serialize_request(lambda: session.list_resources())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose cursor arguments for paginated resource lists

list_resources/list_resource_templates always call the session methods with no pagination arguments, and the new public methods also expose no cursor/params input. On MCP servers that paginate these endpoints, callers can only ever retrieve the first page (despite getting a nextCursor in the result), so resource discovery becomes incomplete for larger datasets. Please forward pagination inputs through these wrappers so clients can fetch subsequent pages.

Useful? React with 👍 / 👎.

Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you resolve all the points mentioned by codex?

@seratch
Copy link
Member

seratch commented Mar 20, 2026

I found one issue with the PR.

src/agents/mcp/server.py adds list_resources() and list_resource_templates() as new wrapper APIs, but both methods drop pagination inputs entirely. The base API and the concrete implementation expose no cursor or params arguments, and the implementation always calls session.list_resources() / session.list_resource_templates() with no pagination state. That means paginated MCP servers can only return the first page, so the new wrapper cannot actually satisfy the docstring claim that it lists “all resources.” The new tests only cover the no-argument happy path, so this regression is not caught.

…templates

seratch's review correctly noted that callers had no way to page through
results beyond the first page on servers that paginate these endpoints.

Changes:
- MCPServer.list_resources(cursor=None) / list_resource_templates(cursor=None)
  base methods now accept an optional opaque cursor string
- _MCPServerWithClientSession concrete implementations forward the cursor
  to session.list_resources(cursor) / session.list_resource_templates(cursor)
- Docstrings document the nextCursor / cursor round-trip pattern
- Test helpers updated to match the new signature
- Two new tests verify cursor is forwarded correctly for both methods
@adityasingh2400
Copy link
Contributor Author

Fixed — list_resources and list_resource_templates now accept an optional cursor: str | None = None argument on both the base class and the concrete implementation. The cursor is forwarded directly to the underlying ClientSession call, so callers can page through results by passing back the nextCursor from each response. Added two new tests verifying cursor is forwarded correctly.

@seratch
Copy link
Member

seratch commented Mar 20, 2026

@codex review this in depth again

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e4bd5224c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +144 to +149
async def list_resources(self) -> ListResourcesResult:
"""Return empty list of resources for fake server."""
return ListResourcesResult(resources=[])

async def list_resource_templates(self) -> ListResourceTemplatesResult:
"""Return empty list of resource templates for fake server."""

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match FakeMCPServer resource method signatures to MCPServer

FakeMCPServer narrows the new interface by omitting the optional cursor parameter on list_resources and list_resource_templates. Any test or helper code that calls these through an MCPServer reference with cursor=... will raise TypeError, so pagination behavior cannot be exercised with this shared fake server.

Useful? React with 👍 / 👎.

Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one last request before merging

"""Read the contents of a specific resource by URI.

Args:
uri: The URI of the resource to read (e.g. ``file:///path/to/file.txt`` or
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you simply mention AnyUrl docstring as reference? We'd like to avoid mentioning specific examples and having our own comment, which can be outdated in the future.

"""Read the contents of a specific resource by URI.

Args:
uri: The URI of the resource to read (e.g. ``file:///path/to/file.txt``).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you simply mention AnyUrl docstring as reference? We'd like to avoid mentioning specific examples and having our own comment, which can be outdated in the future.

@seratch seratch modified the milestones: 0.12.x, 0.13.x Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feature:mcp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants