Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,13 @@ func (c *Client) ReadResource(ctx context.Context, uri string) (*ResourceRespons
return nil, errors.New("invalid response type")
}

var resourceResponse resourceResponseSent
var resourceResponse ResourceResponse
err = json.Unmarshal(responseBytes, &resourceResponse)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal resource response")
}

if resourceResponse.Error != nil {
return nil, resourceResponse.Error
}

return resourceResponse.Response, nil
return &resourceResponse, nil
}

// Ping sends a ping request to the server to check connectivity
Expand Down
23 changes: 23 additions & 0 deletions content_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,29 @@ func (c EmbeddedResource) MarshalJSON() ([]byte, error) {
}
}

// Custom JSON unmarshaling for EmbeddedResource
func (c *EmbeddedResource) UnmarshalJSON(data []byte) error {
// First try to unmarshal as TextResourceContents
var textResource TextResourceContents
err := json.Unmarshal(data, &textResource)
if err == nil && textResource.Text != "" {
c.EmbeddedResourceType = embeddedResourceTypeText
c.TextResourceContents = &textResource
return nil
}

// Then try to unmarshal as BlobResourceContents
var blobResource BlobResourceContents
err = json.Unmarshal(data, &blobResource)
if err == nil && blobResource.Blob != "" {
c.EmbeddedResourceType = embeddedResourceTypeBlob
c.BlobResourceContents = &blobResource
return nil
}

return fmt.Errorf("failed to unmarshal embedded resource: %v", err)
}

type ContentType string

const (
Expand Down
134 changes: 134 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ package mcp_golang

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"testing"
"time"

"github.com/metoro-io/mcp-golang/internal/protocol"
"github.com/metoro-io/mcp-golang/transport"
mcphttp "github.com/metoro-io/mcp-golang/transport/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -220,3 +225,132 @@ func TestServerIntegration(t *testing.T) {
content := result["content"].([]interface{})[0].(map[string]interface{})
assert.Equal(t, "Hello, World!", content["text"])
}

func TestReadResource(t *testing.T) {
// Skip in short mode
if testing.Short() {
t.Skip("skipping in short mode")
}

uri := "test://example"
resourceText := `{"app_name":"MyExampleServer","version":"1.0.0"}`
mimeType := "application/json"

// Start server on a random port
server, addr := startTestServerWithResource(t, uri, resourceText, mimeType)
defer server.Close()

// Create HTTP client transport
clientTransport := mcphttp.NewHTTPClientTransport("/mcp")
clientTransport.WithBaseURL(fmt.Sprintf("http://%s", addr))

// Create client
client := NewClient(clientTransport)

// Initialize
_, err := client.Initialize(context.Background())
assert.NoError(t, err)

// Call ReadResource
response, err := client.ReadResource(context.Background(), uri)

// Verify results
assert.NoError(t, err)
assert.NotNil(t, response)
assert.Equal(t, 1, len(response.Contents))
assert.NotNil(t, response.Contents[0].TextResourceContents)
assert.Equal(t, uri, response.Contents[0].TextResourceContents.Uri)
assert.Equal(t, resourceText, response.Contents[0].TextResourceContents.Text)
}

// Helper function to start a test server with a resource
func startTestServerWithResource(t *testing.T, uri, text, mimeType string) (*http.Server, string) {
// Create HTTP transport
serverTransport := mcphttp.NewHTTPTransport("/mcp")

// Create temporary server
tempAddr := "localhost:0" // Let the OS choose a free port
serverTransport.WithAddr(tempAddr)

// Create server and register the resource
server := NewServer(serverTransport)

err := server.RegisterResource(
uri,
"example",
"Example resource for testing",
mimeType,
func() (*ResourceResponse, error) {
embeddedResource := NewTextEmbeddedResource(uri, text, mimeType)
return NewResourceResponse(embeddedResource), nil
},
)
assert.NoError(t, err)

// Start an HTTP server to handle the actual traffic
mux := http.NewServeMux()
httpServer := &http.Server{Handler: mux}

listener, err := net.Listen("tcp", tempAddr)
assert.NoError(t, err)

// Create a handler that forwards requests to our server
mux.HandleFunc("/mcp", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// Parse the request
var request transport.BaseJSONRPCRequest
err = json.Unmarshal(body, &request)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Process the request with our server
ctx := context.Background()
switch request.Method {
case "initialize":
result, err := server.handleInitialize(ctx, &request, protocol.RequestHandlerExtra{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSONResponse(w, request.Id, result)
case "resources/read":
result, err := server.handleResourceCalls(ctx, &request, protocol.RequestHandlerExtra{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSONResponse(w, request.Id, result)
default:
http.Error(w, "Method not supported", http.StatusBadRequest)
}
})

// Start server in a goroutine
go func() {
if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
t.Logf("HTTP server error: %v", err)
}
}()

// Return the server and the actual address it's listening on
return httpServer, listener.Addr().String()
}

func writeJSONResponse(w http.ResponseWriter, id interface{}, result interface{}) {
w.Header().Set("Content-Type", "application/json")

response := map[string]interface{}{
"jsonrpc": "2.0",
"id": id,
"result": result,
}

json.NewEncoder(w).Encode(response)
}