Skip to content

Feature/pagination functionality #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 10, 2025

Conversation

Jinlkj
Copy link
Contributor

@Jinlkj Jinlkj commented Apr 3, 2025

add pagination functionality: https://spec.modelcontextprotocol.io/specification/2025-03-26/server/utilities/pagination/

  1. add listByPagination function for server to handle list xxx by page
  2. add ListXXXByPage in client/stdio.go client/sse.go to support ListXXX with pagination
  3. fix server_test.go of "List resources with cursor" test

Summary by CodeRabbit

  • New Features
    • Enhanced resource management by introducing pagination for listing resources, templates, prompts, and tools.
    • Improved performance and responsiveness by allowing data to be fetched incrementally with configurable page limits.
    • Streamlined experience for navigating large datasets with seamless multi-page retrieval.
  • Bug Fixes
    • Improved error handling during pagination to ensure proper communication of issues.
  • Tests
    • Enhanced tests for pagination functionality with dynamic cursor generation and default pagination limits.

Copy link
Contributor

coderabbitai bot commented Apr 3, 2025

Walkthrough

This update introduces pagination support across both client and server components. New methods for retrieving resources, resource templates, prompts, and tools by page are added to various client interfaces. A generic helper function is implemented to handle paginated requests uniformly. On the server side, pagination functionality is integrated by adding a configurable pagination limit and a generic pagination function to segment response data. Corresponding changes in the server tests ensure that pagination logic is exercised, including dynamic cursor generation and proper limit settings.

Changes

Files Change Summary
client/client.go, client/sse.go, client/stdio.go Added new paginated methods (ListResourcesByPage, ListResourceTemplatesByPage, ListPromptsByPage, ListToolsByPage) and a generic listByPage helper function to support pagination. Existing listing methods were refactored to invoke these new methods with added pagination and context cancellation logic.
server/server.go, server/server_test.go Introduced server-side pagination by adding a paginationLimit field and a WithPaginationLimit configuration function. Implemented a generic listByPagination function to segment responses and updated list handlers to return paginated results. Test functions were modified to use a dynamically generated cursor and a default pagination limit for validation.
server/stdio.go Modified the readNextLine method in the StdioServer class to improve cancellation handling during input reading, replacing the previous channel mechanism with a done channel and implementing a select statement for better responsiveness to context cancellation.

Possibly related PRs

  • make context available in hooks, add OnRegisterSession hook #92: The changes in the main PR are related to the modifications in the retrieved PR as both involve the addition of new methods for paginated resource management, specifically the ListResourcesByPage, ListResourceTemplatesByPage, ListPromptsByPage, and ListToolsByPage methods, which enhance the functionality of resource listing in a similar context.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61040b7 and 7488f53.

📒 Files selected for processing (1)
  • server/stdio.go (1 hunks)
🔇 Additional comments (1)
server/stdio.go (1)

161-181: Excellent fix for preventing race condition on context cancellation

This change properly addresses the race condition mentioned in the PR comments. By introducing a done channel and checking it in the goroutine, you've ensured that:

  1. The goroutine won't attempt to send data on channels after the main function returns
  2. If context is canceled, the goroutine won't be left dangling or sending on closed channels
  3. All paths are properly handled with select statements to prevent blocking

This is a more robust implementation that follows Go's idiomatic patterns for cancellation and clean goroutine termination.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (9)
server/server_test.go (1)

442-449: Validation of cursor-based resource listing
Adding a test for "List resources with cursor" properly verifies the new pagination logic. Consider adding additional tests for boundary cursor cases (e.g., an empty string cursor or a cursor beyond the last element).

server/server.go (2)

168-173: Pagination limit setter
WithPaginationLimit cleanly adds a configurable page size. Consider validating limit > 0 to avoid edge cases like zero or negative limits.


571-588: Sorted resources before pagination
Sorting by Name before calling listByPagination is consistent with the reflection-based approach. Consider clarifying behavior when multiple resources share the same Name.

client/client.go (2)

112-113: sendRequest in the interface
Exposing an internal request method is useful for generic pagination logic. Ensure correct usage to avoid potential security or logging blind spots.


115-130: Generic listByPage function
This function centralizes pagination logic on the client side. Consider adding stronger error-checking or logging if the server response does not conform to expected structures.

client/sse.go (2)

461-484: Consider memory optimization for large result sets

The current implementation accumulates all results across pages into a single slice. For very large result sets across many pages, this could potentially lead to high memory usage.

Consider offering a streaming alternative or callback-based approach for cases where clients might need to process large datasets:

 func (c *SSEMCPClient) ListResourceTemplates(
 	ctx context.Context,
 	request mcp.ListResourceTemplatesRequest,
 ) (*mcp.ListResourceTemplatesResult, error) {
+	// For large result sets, consider using ListResourceTemplatesStream
 	result, err := c.ListResourceTemplatesByPage(ctx, request)
 	if err != nil {
 		return nil, err
 	}
 	for result.NextCursor != "" {
 		select {
 		case <-ctx.Done():
 			return nil, ctx.Err()
 		default:
 			request.Params.Cursor = result.NextCursor
 			newPageRes, err := c.ListResourceTemplatesByPage(ctx, request)
 			if err != nil {
 				return nil, err
 			}
 			result.ResourceTemplates = append(result.ResourceTemplates, newPageRes.ResourceTemplates...)
 			result.NextCursor = newPageRes.NextCursor
 		}
 	}
 	return result, nil
 }

573-596: Consider adding documentation about pagination behavior

While the implementation is solid, adding a comment explaining the default behavior (fetching all pages) would be helpful for users of this API.

+// ListTools retrieves all tools by automatically handling pagination.
+// It fetches each page sequentially and combines the results until all pages
+// are retrieved or the context is cancelled.
 func (c *SSEMCPClient) ListTools(
 	ctx context.Context,
 	request mcp.ListToolsRequest,
 ) (*mcp.ListToolsResult, error) {
client/stdio.go (2)

351-374: Consider extracting common pagination logic

The pagination logic is duplicated across multiple methods and client implementations. This could potentially be extracted to reduce code duplication.

Consider creating a utility function that handles the pagination logic generically:

// fetchAllPages fetches all pages for a given paginated request
func fetchAllPages[T any, R any](
    ctx context.Context,
    fetchPage func(context.Context, T) (*R, error),
    request T,
    getCursor func(*R) string,
    setCursor func(*T, string),
    appendResults func(*R, *R),
) (*R, error) {
    result, err := fetchPage(ctx, request)
    if err != nil {
        return nil, err
    }
    
    for getCursor(result) != "" {
        select {
        case <-ctx.Done():
            return nil, ctx.Err()
        default:
            setCursor(&request, getCursor(result))
            newPageRes, err := fetchPage(ctx, request)
            if err != nil {
                return nil, err
            }
            appendResults(result, newPageRes)
            // Update cursor for next iteration
            r := result
            setCursor((*T)(nil), getCursor(newPageRes))
        }
    }
    
    return result, nil
}

This is a more advanced refactoring that could be considered in a future PR.


464-487: Consider adding documentation about pagination behavior

While the implementation is solid, adding a comment explaining the default behavior (fetching all pages) would be helpful for users of this API.

+// ListTools retrieves all tools by automatically handling pagination.
+// It fetches each page sequentially and combines the results until all pages
+// are retrieved or the context is cancelled.
 func (c *StdioMCPClient) ListTools(
 	ctx context.Context,
 	request mcp.ListToolsRequest,
 ) (*mcp.ListToolsResult, error) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0e968a and 61040b7.

📒 Files selected for processing (5)
  • client/client.go (5 hunks)
  • client/sse.go (3 hunks)
  • client/stdio.go (3 hunks)
  • server/server.go (8 hunks)
  • server/server_test.go (3 hunks)
🧰 Additional context used
🧬 Code Definitions (5)
server/server_test.go (2)
mcp/types.go (1)
  • JSONRPCMessage (75-75)
server/server.go (1)
  • WithPaginationLimit (169-173)
server/server.go (3)
mcp/types.go (7)
  • Cursor (87-87)
  • Resource (487-502)
  • INVALID_PARAMS (225-225)
  • ListResourcesResult (406-409)
  • PaginatedResult (388-394)
  • ResourceTemplate (506-523)
  • ListResourceTemplatesResult (419-422)
mcp/prompts.go (2)
  • Prompt (43-51)
  • ListPromptsResult (13-16)
mcp/tools.go (2)
  • Tool (69-78)
  • ListToolsResult (19-22)
client/client.go (1)
mcp/types.go (6)
  • ListResourcesRequest (400-402)
  • ListResourcesResult (406-409)
  • ListResourceTemplatesRequest (413-415)
  • ListResourceTemplatesResult (419-422)
  • PaginatedRequest (379-386)
  • Params (104-104)
client/stdio.go (2)
mcp/types.go (7)
  • ListResourcesRequest (400-402)
  • ListResourcesResult (406-409)
  • PaginatedRequest (379-386)
  • Params (104-104)
  • Cursor (87-87)
  • ListResourceTemplatesRequest (413-415)
  • ListResourceTemplatesResult (419-422)
mcp/tools.go (2)
  • ListToolsRequest (13-15)
  • ListToolsResult (19-22)
client/sse.go (3)
mcp/types.go (7)
  • ListResourcesRequest (400-402)
  • ListResourcesResult (406-409)
  • PaginatedRequest (379-386)
  • Params (104-104)
  • Cursor (87-87)
  • ListResourceTemplatesRequest (413-415)
  • ListResourceTemplatesResult (419-422)
mcp/prompts.go (2)
  • ListPromptsRequest (7-9)
  • ListPromptsResult (13-16)
mcp/tools.go (2)
  • ListToolsRequest (13-15)
  • ListToolsResult (19-22)
🔇 Additional comments (25)
server/server_test.go (3)

5-5: Use of "encoding/base64" import
Importing "encoding/base64" is appropriate for generating an encoded cursor.


434-434: Dynamic cursor generation
Defining a base64-encoded cursor for "My Resource" helps realistically test pagination. This approach is clear and maintainable.


1129-1129: Fixed pagination limit
WithPaginationLimit(2) is a good default for testing. If appropriate, consider adding tests for edge cases (e.g., zero limit, large limit) to ensure robust coverage.

server/server.go (4)

6-6: New import "encoding/base64"
This is necessary for decoding/encoding the cursor. Looks good.


603-620: Resource templates pagination
The same reflection-based pagination is correctly applied to templates. Keep an eye on potential collisions in template names if they are not guaranteed to be unique.


703-717: Prompts pagination
Similarly, sorting prompts by Name and then paginating is consistent. If future prompt types contain no Name field, ensure consistent handling.


770-778: Tools pagination
Pagination logic for tools looks aligned with the rest of the code. Watch out for collisions if multiple tools share the same Name.

client/client.go (5)

6-7: New imports "encoding/json" and "fmt"
These imports are necessary for unmarshalling responses and constructing formatted errors.


23-28: ListResourcesByPage method
Introducing a manual paginated listing makes sense given the new server pagination. Ensure adequate client-side testing of boundary cursors.


35-41: ListResourceTemplatesByPage method
Provides consistent pagination for resource templates. Well aligned with the server.


61-66: ListPromptsByPage method
Good addition for prompt pagination on the client side. Matches the newly introduced server logic.


79-84: ListToolsByPage method
Keeps the client interface consistent, ensuring all resource types support pagination.

client/sse.go (6)

413-423: Good implementation of pagination support

The new ListResourcesByPage method properly implements the pagination functionality by leveraging a generic helper. This allows clients to fetch resources one page at a time when needed.


425-448: Well-structured pagination handling with automatic cursor management

This implementation efficiently handles multi-page results while properly managing context cancellation. The method correctly:

  1. Fetches initial page results
  2. Checks for additional pages via NextCursor
  3. Updates the cursor for subsequent requests
  4. Concatenates results across pages
  5. Handles context cancellation appropriately

450-459: Consistent pagination implementation for resource templates

The implementation follows the same pattern as the resources pagination, maintaining consistency across the codebase.


514-523: Consistent pagination implementation for prompts

The implementation follows the same pattern as the other pagination methods, maintaining consistency across the codebase.


525-548: Good error handling and context awareness

The implementation properly checks for errors at each step and respects context cancellation, allowing for clean termination of long-running pagination operations.


562-571: Consistent pagination implementation for tools

The implementation follows the same pattern as the other pagination methods, maintaining consistency across the codebase.

client/stdio.go (7)

303-313: Good implementation of pagination support

The new ListResourcesByPage method properly implements the pagination functionality by leveraging a generic helper. Implementation is consistent with the SSEMCPClient version.


315-338: Well-structured pagination handling with automatic cursor management

This implementation efficiently handles multi-page results while properly managing context cancellation, following the same pattern as in the SSEMCPClient implementation.


340-349: Consistent pagination implementation for resource templates

The implementation follows the same pattern as the resources pagination, maintaining consistency across both client implementations.


405-414: Consistent pagination implementation for prompts

The implementation follows the same pattern as the other pagination methods, maintaining consistency across the codebase.


416-439: Good error handling and context awareness

The implementation properly checks for errors at each step and respects context cancellation, allowing for clean termination of long-running pagination operations.


453-462: Consistent pagination implementation for tools

The implementation follows the same pattern as the other pagination methods, maintaining consistency across the codebase.


315-487:

✅ Verification successful

Verify implementation with usage tests

The pagination implementation appears correct, but it would be good to verify it works correctly with various test scenarios like empty results, single page results, and multi-page results.

Run the following script to check if there are tests covering the pagination functionality:


🏁 Script executed:

#!/bin/bash
# Check for tests that verify pagination functionality

echo "Checking for pagination tests in client implementations..."
rg -A 3 -B 3 "Test.*Pagination|test.*pagination" --glob "**/*_test.go"

echo "Checking for tests that might validate cursor handling..."
rg -A 3 -B 3 "cursor|Cursor" --glob "**/*_test.go"

Length of output: 2259


Pagination Implementation Verified by Tests

The existing tests in server/server_test.go (specifically in TestMCPServer_HandlePagination) confirm that the pagination functionality works correctly for different scenarios (empty results, single page, and multi-page responses). No further changes are necessary.

@Jinlkj
Copy link
Contributor Author

Jinlkj commented Apr 7, 2025

@ezynda3 There's a race problem of server/stdio.go readNextLine function.
When the context is canceled, the following may occur:
The main goroutine returns via ctx.Done(),
but the background goroutine is still attempting to send data to the channel.
At this point, the channel may have already been closed, leading to a "send on closed channel" error.
I suggest change it to

func (s *StdioServer) readNextLine(ctx context.Context, reader *bufio.Reader) (string, error) {
	readChan := make(chan string, 1)
	errChan := make(chan error, 1)
	done := make(chan struct{})
	defer close(done)

	go func() {
		select {
		case <-done:
			return
		default:
			line, err := reader.ReadString('\n')
			if err != nil {
				select {
				case errChan <- err:
				case <-done:

				}
				return
			}
			select {
			case readChan <- line:
			case <-done:
			}
		}
	}()

	select {
	case <-ctx.Done():
		return "", ctx.Err()
	case err := <-errChan:
		return "", err
	case line := <-readChan:
		return line, nil
	}
}

@ezynda3 ezynda3 merged commit c7390fe into mark3labs:main Apr 10, 2025
2 checks passed
leavez added a commit to leavez/mcp-go that referenced this pull request Apr 11, 2025
ezynda3 pushed a commit that referenced this pull request Apr 16, 2025
* add transport layer interface

* universal client

* impl sse & stdio transport based on the original client

* refactor old client to provide compibility

* rename

* remove old client types

* add test for stdio transport

* rename 'done' to 'closed', to distinguish with ctx.Done

* add cancelSSEStream for better handling of close

* fix connection leak when start timeout

* avoid multiple starting

* use atomic for closed to be more natural compared to started

* fix leak of timer

* Create sse_test.go

* enforce test

* add comment

* sse: add custom header in start request

* update comment

* comment

* cover #88

* cover #107

* fix demo sse server in race test
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.

2 participants