Skip to content
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

[#67] Fix Client's ReadResource method #68

Conversation

rvoh-emccaleb
Copy link

@rvoh-emccaleb rvoh-emccaleb commented Jan 27, 2025

Pre-requisite

Review/merge #66, first.

Issue

#67

Explanation of bug

The Client's current ReadResource() method does not correctly parse the response from the Protocol's request to the Server.

That response, returned by the Server, is ultimately enforced to be of type *ResourceResponse when the resource is registered at server startup. This type differs from what the Client is currently written to expect.

Solution

Updated the Client's ReadResponse() method to expect type *ResourceResponse.

I can now set up the server, locally, with:

err = server.RegisterResource(
	"example_resource.txt",
	"example",
	"Description for example resource",
	"text/plain",
	func() (*mcp.ResourceResponse, error) {
		content, err := os.ReadFile("internal/mcpserver/resources/example_resource.txt")
		if err != nil {
			return nil, fmt.Errorf("failed to read example resource: %w", err)
		}

		embeddedResource := mcp.NewTextEmbeddedResource("example_resource.txt", string(content), "text/plain")
		
		return mcp.NewResourceResponse(embeddedResource), nil
	},
)

And I can run the client, locally:

clientTransport := http.NewHTTPClientTransport("/mcp")
clientTransport.WithBaseURL("http://localhost:8080")
...
if _, err := client.Initialize(context.Background()); err != nil {
	log.Fatalf("failed to initialize client: %v", err)
}
...
exampleResponse, err := client.ReadResource(context.Background(), "example_resource.txt")
if err != nil {
	log.Fatalf("failed to read example resource: %v", err)
}

good := exampleResponse != nil && len(exampleResponse.Contents) > 0 && exampleResponse.Contents[0] != nil && exampleResponse.Contents[0].TextResourceContents != nil
if !good {
	log.Fatalf("invalid resource response received: %+v", exampleResponse)
}

log.Printf("example resource response: %s", exampleResponse.Contents[0].TextResourceContents.Text)

Running the client outputs:

example resource response: <contents-of-internal/mcpserver/resources/example_resource.txt>

@@ -85,14 +85,51 @@ type EmbeddedResource struct {

// Custom JSON marshaling for EmbeddedResource
func (c EmbeddedResource) MarshalJSON() ([]byte, error) {
type wrapper struct {
Copy link
Author

Choose a reason for hiding this comment

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

The changes in this file are written to satisfy the following constraint:

  1. Don't break the existing marshaling/unmarshaling API for EmbeddedResource.
    1. Add an embeddedResourceType field (don't touch existing fields).
    2. Continue to allow the multiple content types and schemas (BlobResourceContents, TextResourceContents) over one endpoint.

switch c.EmbeddedResourceType {
case embeddedResourceTypeText:
c.TextResourceContents = new(TextResourceContents)
return json.Unmarshal(wrapper.Raw, c.TextResourceContents)
Copy link
Author

@rvoh-emccaleb rvoh-emccaleb Jan 27, 2025

Choose a reason for hiding this comment

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

I don't like unmarshaling twice, but given the API supports multiple types/schemas that are identified in the body (not in headers), I don't currently see a way around it.

@rvoh-emccaleb
Copy link
Author

Temporarily have to close this due to company OSS policies

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.

1 participant