Skip to content

Commit

Permalink
✨ Make server work with mcp inspector on SSE
Browse files Browse the repository at this point in the history
  • Loading branch information
wesen committed Jan 21, 2025
1 parent 46a4662 commit cb56757
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 40 deletions.
2 changes: 2 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [X] Add debug logging
- [ ] make web ui to easily debug / interact
- [ ] add notification handler
- [ ] add resource templates

### Bugs
- [x] BUG: figure out why closing the client seems to hang
Expand All @@ -25,6 +26,7 @@
- [ ] allow config file for all settings
- [ ] figure out how to easily register bash commands to the MCP
- [ ] dynamic loading / enabling / removing servers
- [ ] add resource templates

- [X] Allow debug logging
- [x] Implement missing SSE methods
Expand Down
14 changes: 13 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,16 @@ Enhanced SSE client to properly handle endpoint events and session management:

Ensure list operations return empty arrays instead of null when no results are available. This fixes type validation errors in the client.

- Modified SSE server to convert nil results to empty arrays for list operations
- Added structured response types (ListPromptsResult, ListResourcesResult, ListToolsResult) for list operations
- Improved type handling for list operation results with proper interface conversions
- Ensured empty arrays are always returned instead of null

## Structured List Operation Responses

Added proper response types for list operations to ensure consistent JSON encoding:

- Created ListPromptsResult, ListResourcesResult, and ListToolsResult types with correct protocol types
- Fixed type mismatches between service interfaces and response types
- Improved type handling with proper interface conversions
- Ensured empty arrays are always returned instead of null
- Fixed JSON response structure to match API specification
123 changes: 84 additions & 39 deletions pkg/server/sse.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"net"
"net/http"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -42,6 +41,21 @@ type SSEClient struct {
userAgent string
}

type ListPromptsResult struct {
Prompts []protocol.Prompt `json:"prompts"`
NextCursor string `json:"nextCursor"`
}

type ListResourcesResult struct {
Resources []protocol.Resource `json:"resources"`
NextCursor string `json:"nextCursor"`
}

type ListToolsResult struct {
Tools []protocol.Tool `json:"tools"`
NextCursor string `json:"nextCursor"`
}

// NewSSEServer creates a new SSE server instance
func NewSSEServer(logger zerolog.Logger, ps services.PromptService, rs services.ResourceService, ts services.ToolService, is services.InitializeService, port int) *SSEServer {
return &SSEServer{
Expand Down Expand Up @@ -395,48 +409,35 @@ func (s *SSEServer) handleMessages(w http.ResponseWriter, r *http.Request) {
cursor = params.Cursor
}

var result interface{}
var nextCursor string
var err error
var resultJSON json.RawMessage
var marshalErr error

switch request.Method {
case "prompts/list":
result, nextCursor, err = s.promptService.ListPrompts(ctx, cursor)
case "resources/list":
result, nextCursor, err = s.resourceService.ListResources(ctx, cursor)
case "tools/list":
result, nextCursor, err = s.toolService.ListTools(ctx, cursor)
}

if err != nil {
data, _ := json.Marshal(err.Error())
response = &protocol.Response{
JSONRPC: "2.0",
ID: request.ID,
Error: &protocol.Error{
Code: -32603,
Message: "Internal error",
Data: json.RawMessage(data),
},
}
} else {
// Ensure we have an empty array instead of null for empty results
if result == nil {
switch request.Method {
case "prompts/list":
result = []interface{}{}
case "resources/list":
result = []interface{}{}
case "tools/list":
result = []interface{}{}
prompts, nextCursor, err := s.promptService.ListPrompts(ctx, cursor)
if err != nil {
data, _ := json.Marshal(err.Error())
response = &protocol.Response{
JSONRPC: "2.0",
ID: request.ID,
Error: &protocol.Error{
Code: -32603,
Message: "Internal error",
Data: json.RawMessage(data),
},
}
break
}

resultMap := map[string]interface{}{
strings.TrimSuffix(request.Method, "/list"): result,
"nextCursor": nextCursor,
if prompts == nil {
prompts = []protocol.Prompt{}
}
resultJSON, err := s.marshalJSON(resultMap)
resultJSON, marshalErr = s.marshalJSON(ListPromptsResult{
Prompts: prompts,
NextCursor: nextCursor,
})

case "resources/list":
resources, nextCursor, err := s.resourceService.ListResources(ctx, cursor)
if err != nil {
data, _ := json.Marshal(err.Error())
response = &protocol.Response{
Expand All @@ -448,12 +449,56 @@ func (s *SSEServer) handleMessages(w http.ResponseWriter, r *http.Request) {
Data: json.RawMessage(data),
},
}
} else {
break
}
if resources == nil {
resources = []protocol.Resource{}
}
resultJSON, marshalErr = s.marshalJSON(ListResourcesResult{
Resources: resources,
NextCursor: nextCursor,
})

case "tools/list":
tools, nextCursor, err := s.toolService.ListTools(ctx, cursor)
if err != nil {
data, _ := json.Marshal(err.Error())
response = &protocol.Response{
JSONRPC: "2.0",
ID: request.ID,
Result: resultJSON,
Error: &protocol.Error{
Code: -32603,
Message: "Internal error",
Data: json.RawMessage(data),
},
}
break
}
if tools == nil {
tools = []protocol.Tool{}
}
resultJSON, marshalErr = s.marshalJSON(ListToolsResult{
Tools: tools,
NextCursor: nextCursor,
})
}

if marshalErr != nil {
data, _ := json.Marshal(marshalErr.Error())
response = &protocol.Response{
JSONRPC: "2.0",
ID: request.ID,
Error: &protocol.Error{
Code: -32603,
Message: "Internal error",
Data: json.RawMessage(data),
},
}
} else if response == nil {
response = &protocol.Response{
JSONRPC: "2.0",
ID: request.ID,
Result: resultJSON,
}
}

Expand Down

0 comments on commit cb56757

Please sign in to comment.