How the MCP Endpoint is Configured Using Gin and the MCP SDK

The MCP endpoint is configured by wrapping the official MCP SDK's NewStreamableHTTPHandler with Gin's WrapH function, registering it on the /mcp route, and initializing the underlying *mcp.Server instance with custom tool definitions.

The xpzouying/xiaohongshu-mcp repository demonstrates a production-ready pattern for exposing a Model Context Protocol (MCP) endpoint within an existing Gin web application. This implementation uses the official MCP SDK for Go to handle protocol semantics while leveraging Gin's routing and middleware capabilities.

Registering the MCP Route with Gin

The HTTP surface of the MCP endpoint is established in routes.go through a dedicated setup function that bridges the MCP SDK's handler interface with Gin's router.

Creating the Streamable HTTP Handler

The core of the integration relies on mcp.NewStreamableHTTPHandler, which creates a streamable HTTP transport compatible with the MCP specification. This handler requires a function that returns the *mcp.Server instance rather than the instance itself, enabling lazy resolution and proper request scoping.

// routes.go – inside setupRoutes
mcpHandler := mcp.NewStreamableHTTPHandler(
    func(r *http.Request) *mcp.Server { return appServer.mcpServer },
    &mcp.StreamableHTTPOptions{
        JSONResponse: true, // Enable JSON output for all tool calls
    },
)

Attaching Routes to the Gin Router

Because the MCP SDK handles its own internal routing based on the request path and method, the Gin router must catch all requests under the /mcp prefix and forward them to the wrapped handler. The gin.WrapH utility converts a standard http.Handler into a Gin-compatible handler function.

// routes.go – route registration
router.Any("/mcp", gin.WrapH(mcpHandler))
router.Any("/mcp/*path", gin.WrapH(mcpHandler))

This configuration ensures that both the base path /mcp and any sub-paths (such as /mcp/initialize or /mcp/tools/call) are routed to the same MCP handler instance.

Initializing the MCP Server Instance

Before the HTTP handler can process requests, the underlying *mcp.Server must be constructed and populated with tool definitions. This occurs in mcp_server.go through the InitMCPServer function.

Server Configuration and Metadata

The server is instantiated using mcp.NewServer, which requires an Implementation struct defining the server name and version. This metadata is exposed to MCP clients during the initialization handshake.

// mcp_server.go – InitMCPServer
func InitMCPServer(appServer *AppServer) *mcp.Server {
    server := mcp.NewServer(
        &mcp.Implementation{
            Name:    "xiaohongshu-mcp",
            Version: "2.0.0",
        },
        nil,
    )
    // Tool registration happens here...
    return server
}

Tool Registration

Custom tools—such as check_login_status and publish_content—are registered with the server via the registerTools function. This binds the tool definitions and their handler functions to the MCP server instance, making them available to clients.

// mcp_server.go – tool registration
registerTools(server, appServer)
logrus.Info("MCP Server initialized with official SDK")

The registerTools function typically iterates over a collection of tool definitions, calling server.RegisterTool for each capability exposed by the Xiaohongshu integration.

Wiring the MCP Server into the Application

The final piece of the configuration connects the MCP server to the broader application context, allowing tools to access shared services such as the Xiaohongshu API client.

The AppServer Struct Pattern

The AppServer struct defined in app_server.go acts as the dependency container. It holds references to both the business logic services (xiaohongshuService) and the MCP server (mcpServer).

// app_server.go – AppServer definition
type AppServer struct {
    xiaohongshuService *XiaohongshuService
    mcpServer          *mcp.Server
}

Initialization Order

The NewAppServer constructor carefully sequences the initialization to avoid circular dependencies. The struct is allocated first, then the MCP server is created with a reference to the partially initialized AppServer. This allows tool handlers to access the xiaohongshuService via the AppServer receiver.

// app_server.go – NewAppServer
func NewAppServer(xiaohongshuService *XiaohongshuService) *AppServer {
    appServer := &AppServer{
        xiaohongshuService: xiaohongshuService,
    }
    // Build the MCP server after the AppServer struct exists
    appServer.mcpServer = InitMCPServer(appServer)
    return appServer
}

This wiring pattern ensures that when the Gin router starts and the MCP endpoint receives its first request, the handler has full access to the initialized tool set and underlying business services.

Summary

  • Route Registration: The MCP endpoint is exposed via mcp.NewStreamableHTTPHandler wrapped with gin.WrapH and mounted on /mcp and /mcp/*path in routes.go.
  • Server Initialization: The *mcp.Server is constructed in mcp_server.go using mcp.NewServer with implementation metadata and populated via registerTools.
  • Application Integration: AppServer in app_server.go orchestrates the initialization order, ensuring the MCP server is created after the struct exists so tools can access xiaohongshuService.
  • Handler Pattern: The SDK uses a function-based server resolver func(r *http.Request) *mcp.Server to support request-scoped server access while maintaining compatibility with Gin's handler interface.

Frequently Asked Questions

What is the exact URL path for the MCP endpoint?

The MCP endpoint is served at the base path /mcp with support for all sub-paths via /mcp/*path. Both routes are registered using router.Any() in Gin, which allows the MCP SDK's streamable HTTP handler to process POST requests for initialization, tool listing, and tool invocation under the same endpoint structure.

Why does the handler use a function that returns the server instead of the server directly?

The mcp.NewStreamableHTTPHandler constructor accepts a resolver function func(r *http.Request) *mcp.Server rather than a static server pointer. This design allows for request-scoped server resolution, potential per-request middleware injection, and lazy initialization patterns. In the xiaohongshu-mcp implementation, this function simply returns appServer.mcpServer, but the indirection preserves compatibility with the SDK's intended architecture.

How are custom tools registered with the MCP server?

Tool registration occurs in mcp_server.go within the InitMCPServer function. After creating the server with mcp.NewServer, the code calls registerTools(server, appServer), which iterates over the application's tool definitions (such as check_login_status and publish_content) and registers each one with the server instance. This makes the tools discoverable via the MCP tools/list endpoint and invocable through tools/call.

Can the MCP endpoint be mounted on a different base path?

Yes, while the current implementation uses /mcp, you can modify the route registration in routes.go to use any base path. Simply change the strings in router.Any("/mcp", ...) and router.Any("/mcp/*path", ...) to your desired path (for example, /api/mcp or /v1/mcp). The MCP SDK's streamable handler is path-agnostic and will function correctly as long as all requests are routed to the wrapped handler.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →