Skip to content

Feat compatible config merge for stdio mcp servers#37

Merged
iFurySt merged 16 commits into
AmoyLab:mainfrom
LeoLiuYan:feat/support_proto_stdio
May 14, 2025
Merged

Feat compatible config merge for stdio mcp servers#37
iFurySt merged 16 commits into
AmoyLab:mainfrom
LeoLiuYan:feat/support_proto_stdio

Conversation

@LeoLiuYan
Copy link
Copy Markdown
Contributor

@LeoLiuYan LeoLiuYan commented May 7, 2025

Summary by Sourcery

Enable full support for stdio-based MCP servers alongside existing HTTP and SSE protocols by expanding configuration schemas, merging logic, and server dispatch to handle multiple protocol types uniformly.

New Features:

  • Add stdio transport and client library for JSON-RPC communication with subprocess-based MCP servers
  • Introduce stdio and SSE server configuration types and merge them alongside HTTP configs
  • Support protocol-specific configuration merging returning separate configs for HTTP, stdio, and SSE

Enhancements:

  • Refactor server initialization and route registration to dispatch tool listing and calls based on protocol type
  • Unify and simplify JSON-RPC request/response types in pkg/mcp, renaming fields for consistency
  • Extend MergeConfigs implementation with dedicated merge functions per protocol
  • Populate server state with prefix-to-proto mappings and per-protocol server/tool configuration

@LeoLiuYan LeoLiuYan marked this pull request as ready for review May 13, 2025 14:17
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 13, 2025

Reviewer's Guide

This PR refactors configuration merging to produce separate HTTP, stdio, and SSE configs, extends the core server to dispatch requests based on protocol type (with new fetch/invoke helpers for HTTP and stdio backends), introduces a stdio‐based MCP client transport and wrapper APIs, overhauls the pkg/mcp API types (JSON‐RPC, tools, capabilities), and applies minor fixes (ID field normalization, MIMEType renames, dependency updates).

File-Level Changes

Change Details Files
Split and extend configuration merging to support HTTP, stdio, and SSE protocols
  • MergeConfigs now returns distinct MCPConfig slices and delegates to mergeHTTPConfig, mergeStdioConfig, and mergeSSEConfig
  • Added ProtoType, StdioServerConfig, and SSEServerConfig fields in common config and database model
  • Updated ToMCPConfig/FromMCPConfig to marshal/unmarshal the new protocol‐specific fields
internal/mcp/storage/helper/merger.go
internal/common/config/mcp.go
internal/mcp/storage/model.go
Enhance server initialization and request dispatch for multiple backend protocols
  • State tracks prefixToProtoType, prefixToStdioServerConfig, prefixToSSEServerConfig
  • RegisterRoutes and initState now accept []MCPConfig and build state per protocol
  • handlePostMessage/handleMCPRequest switch on protoType and use fetchHTTPToolList/fetchStdioToolList and invokeHTTPTool/invokeStdioTool helpers
  • Normalized JSONRPCRequest ID handling and Params unmarshalling across SSE and streamable endpoints
internal/core/server.go
internal/core/sse.go
internal/core/streamable.go
internal/core/response.go
internal/core/tool.go
Add stdio‐based MCP client transport and client API
  • Implemented transport.Stdio to spawn subprocesses and handle JSON‐RPC over stdio
  • Defined Interface for transports and client-side request/notification flows
  • Exposed NewStdioMCPClient and high‐level Client methods (Initialize, ListTools, CallTool, OnNotification)
internal/core/mcpclient/transport/stdio.go
internal/core/mcpclient/transport/interface.go
internal/core/mcpclient/client.go
internal/core/mcpclient/stdio.go
Overhaul pkg/mcp API types and utilities
  • Consolidated JSONRPCRequest/Response/Notification structs and normalized the ID field
  • Introduced ServerCapabilities, ClientCapabilities, and NotificationParams
  • Converted ToolSchema to Tool struct and updated CallToolParams/Result
  • Added parsing utilities (ParseCallToolResult, CoverToStdioClientEnv)
pkg/mcp/server_types.go
pkg/mcp/client_types.go
pkg/mcp/interface.go
pkg/mcp/utils.go
pkg/mcp/tools.go
Miscellaneous refactors and dependency updates
  • Renamed MimeType to MIMEType and fixed ID/Id inconsistencies in error and success responses
  • Adjusted JSON unmarshal calls to handle byte slices for req.Params
  • Added yosida95/uritemplate dependency in go.mod
go.mod
internal/core/sse.go
internal/core/streamable.go
internal/core/response.go
internal/core/tool.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey @LeoLiuYan - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 6 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread internal/core/sse.go Outdated
var params mcp.InitializeRequestParams
if err := json.Unmarshal(req.Params, &params); err != nil {
s.sendProtocolError(c, req.Id, "Invalid initialize parameters", http.StatusBadRequest, mcp.ErrorCodeInvalidParams)
if err := json.Unmarshal(req.Params.([]byte), &params); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Type assertion on req.Params may panic if not []byte.

Use a type switch or explicit check to avoid panics, or ensure callers always supply req.Params as []byte.

Comment thread internal/core/response.go Outdated

// sendSuccessResponse sends a successful response
func (s *Server) sendSuccessResponse(c *gin.Context, conn session.Connection, req mcp.JSONRPCRequest, result any, isSSE bool) {
resultBytes, _ := json.Marshal(result)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Ignoring errors from json.Marshal may hide serialization issues.

Handle the error returned by json.Marshal instead of ignoring it. Return a protocol error if marshaling fails to avoid silent failures.

Comment thread internal/core/streamable.go Outdated
var params mcp.InitializeRequestParams
if err := json.Unmarshal(req.Params, &params); err != nil {
s.sendProtocolError(c, req.Id, fmt.Sprintf("invalid initialize parameters: %v", err), http.StatusBadRequest, mcp.ErrorCodeInvalidParams)
if err := json.Unmarshal(req.Params.([]byte), &params); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Type assertion on req.Params to []byte may panic.

Use a type switch or ensure req.Params is a []byte before unmarshaling to avoid panics.

Comment thread internal/core/server.go Outdated
Comment on lines +178 to +179
newState.prefixToStdioServerConfig[prefix] = &cfg.StdioServer
newState.prefixToSSEServerConfig[prefix] = &cfg.SSEServer
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Storing pointers to config fields that may be overwritten in the next iteration.

Copy the StdioServer and SSEServer structs (or store their values) instead of taking pointers, so each prefix retains its own config rather than all referencing the final loop variable.

Comment thread internal/mcp/storage/helper/merger.go Outdated
}

return mergedConfig, nil
return []*config.MCPConfig{mergedHTTPConfig, mergedStdioConfig, mergedSSEConfig}, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Returning all protocol configs even if empty may cause confusion downstream.

Filter out empty configs and return only those with merged data.

Comment thread internal/mcp/storage/helper/merger.go Outdated
}

func mergeConfigStdio(base, override config.StdioServerConfig) config.StdioServerConfig {
return override
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Merging Stdio/SSE config by overwriting may lose data from base config.

These functions discard the base config by returning only the override. For partial merges, implement a field-by-field merge to avoid data loss.

Suggested implementation:

func mergeConfigStdio(base, override config.StdioServerConfig) config.StdioServerConfig {
    // Start from the base config…
    merged := base

    // …then selectively override only the non-default override values

    // Enabled flag: override only if explicitly true
    if override.Enabled {
        merged.Enabled = true
    }

    // Address (or Host): override if provided
    if override.Address != "" {
        merged.Address = override.Address
    }

    // Port: override if non-zero
    if override.Port != 0 {
        merged.Port = override.Port
    }

    // TLS settings: override if non-nil
    if override.TLS != nil {
        merged.TLS = override.TLS
    }

    // (Repeat for any other StdioServerConfig fields, e.g. timeouts, buffer sizes, etc.)

    return merged
}

Make sure the above fields (Enabled, Address, Port, TLS, etc.) match those in your actual config.StdioServerConfig. For every field in that struct, add a corresponding “if override.FIELD is non-default, merged.FIELD = override.FIELD” clause.

@iFurySt iFurySt merged commit 576bf9d into AmoyLab:main May 14, 2025
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