Multiple SSE server in single MCP server with different tool set #344
Unanswered
gauravsaralMs
asked this question in
Q&A
Replies: 2 comments
-
was able to get something working, but not sure it is correct or not file to create and run server
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
// Create hooks first
hooks := &server.Hooks{
OnRegisterSession: []server.OnRegisterSessionHookFunc{},
}
// Create a single MCP server
mcpServer := server.NewMCPServer(
"test-server",
"1.0.0",
server.WithToolCapabilities(true),
server.WithHooks(hooks),
)
// Add hooks for client initialization
hooks.OnBeforeInitialize = append(hooks.OnBeforeInitialize, func(ctx context.Context, id any, message *mcp.InitializeRequest) {
log.Printf("Before client initialization for session: %s", server.ClientSessionFromContext(ctx).SessionID())
})
hooks.OnAfterInitialize = append(hooks.OnAfterInitialize, func(ctx context.Context, id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) {
session := server.ClientSessionFromContext(ctx)
log.Printf("After client initialization for session: %s", session.SessionID())
log.Printf("Client info: %+v", message.Params.ClientInfo)
log.Printf("Session initialized: %v", session.Initialized())
if sessionWithTools, ok := session.(server.SessionWithTools); ok {
tools := sessionWithTools.GetSessionTools()
log.Printf("Session tools during initialization: %v", tools)
}
})
// Add the register session hook after server creation
hooks.OnRegisterSession = append(hooks.OnRegisterSession, func(ctx context.Context, session server.ClientSession) {
log.Printf("Session registration hook called for session ID: %s", session.SessionID())
// Get the server identifier from the context
serverID, ok := ctx.Value("server").(string)
if !ok {
log.Printf("No server ID found in context for session: %s", session.SessionID())
return
}
log.Printf("Server ID from context: %s", serverID)
// Initialize the session
session.Initialize()
log.Printf("Session initialized: %v", session.Initialized())
// Add session-specific tools based on the server identifier
if serverID == "sse1" {
err := mcpServer.AddSessionTool(
session.SessionID(),
mcp.NewTool("session1-tool", mcp.WithDescription("A tool specific to SSE1 server")),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return mcp.NewToolResultText("This is a response from session1-tool"), nil
},
)
if err != nil {
log.Printf("Failed to add session1-tool: %v", err)
} else {
log.Printf("Successfully added session1-tool for session: %s", session.SessionID())
// Verify the tool was added
if sessionWithTools, ok := session.(server.SessionWithTools); ok {
tools := sessionWithTools.GetSessionTools()
log.Printf("Session tools after adding: %v", tools)
}
}
} else if serverID == "sse2" {
err := mcpServer.AddSessionTool(
session.SessionID(),
mcp.NewTool("session2-tool", mcp.WithDescription("A tool specific to SSE2 server")),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return mcp.NewToolResultText("This is a response from session2-tool"), nil
},
)
if err != nil {
log.Printf("Failed to add session2-tool: %v", err)
} else {
log.Printf("Successfully added session2-tool for session: %s", session.SessionID())
// Verify the tool was added
if sessionWithTools, ok := session.(server.SessionWithTools); ok {
tools := sessionWithTools.GetSessionTools()
log.Printf("Session tools after adding: %v", tools)
}
}
}
})
// Add multiple tools to the MCP server
mcpServer.AddTools(
// Text processing tool
server.ServerTool{
Tool: mcp.NewTool("text-processor", mcp.WithDescription("Process text with various operations like uppercase, lowercase, and word count")),
Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
text, err := request.RequireString("text")
if err != nil {
return mcp.NewToolResultError("text parameter is required"), nil
}
operation, err := request.RequireString("operation")
if err != nil {
return mcp.NewToolResultError("operation parameter is required"), nil
}
var result string
switch operation {
case "uppercase":
result = strings.ToUpper(text)
case "lowercase":
result = strings.ToLower(text)
case "wordcount":
result = fmt.Sprintf("Word count: %d", len(strings.Fields(text)))
default:
result = "Invalid operation"
}
return mcp.NewToolResultText(result), nil
},
},
// Calculator tool
server.ServerTool{
Tool: mcp.NewTool("calculator", mcp.WithDescription("Perform basic mathematical calculations")),
Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
num1, err := request.RequireFloat("num1")
if err != nil {
return mcp.NewToolResultError("num1 parameter is required"), nil
}
num2, err := request.RequireFloat("num2")
if err != nil {
return mcp.NewToolResultError("num2 parameter is required"), nil
}
operation, err := request.RequireString("operation")
if err != nil {
return mcp.NewToolResultError("operation parameter is required"), nil
}
var result float64
switch operation {
case "add":
result = num1 + num2
case "subtract":
result = num1 - num2
case "multiply":
result = num1 * num2
case "divide":
if num2 == 0 {
return mcp.NewToolResultError("Error: Division by zero"), nil
}
result = num1 / num2
default:
return mcp.NewToolResultError("Invalid operation"), nil
}
return mcp.NewToolResultText(fmt.Sprintf("Result: %.2f", result)), nil
},
},
// System info tool
server.ServerTool{
Tool: mcp.NewTool("system-info", mcp.WithDescription("Get basic system information")),
Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
hostname, _ := os.Hostname()
info := fmt.Sprintf("Hostname: %s\nTime: %s", hostname, time.Now().Format(time.RFC3339))
return mcp.NewToolResultText(info), nil
},
},
)
// 2. Create two SSE servers on the same port but different endpoints
sseServer1 := server.NewSSEServer(
mcpServer,
server.WithBaseURL("http://localhost:8080"),
server.WithSSEEndpoint("/sse1"),
server.WithMessageEndpoint("/message1"),
)
sseServer2 := server.NewSSEServer(
mcpServer,
server.WithBaseURL("http://localhost:8080"), // Same port as sseServer1
server.WithSSEEndpoint("/sse2"),
server.WithMessageEndpoint("/message2"),
)
// Create a new HTTP server to handle both SSE servers
mux := http.NewServeMux()
// Create custom handlers that add the server identifier to the context
mux.Handle("/sse1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "server", "sse1")
sseServer1.SSEHandler().ServeHTTP(w, r.WithContext(ctx))
}))
mux.Handle("/message1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "server", "sse1")
sseServer1.MessageHandler().ServeHTTP(w, r.WithContext(ctx))
}))
mux.Handle("/sse2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "server", "sse2")
sseServer2.SSEHandler().ServeHTTP(w, r.WithContext(ctx))
}))
mux.Handle("/message2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "server", "sse2")
sseServer2.MessageHandler().ServeHTTP(w, r.WithContext(ctx))
}))
// Create HTTP server
srv := &http.Server{
Addr: ":8080",
Handler: mux,
}
// Channel to listen for errors coming from the listener.
serverErrors := make(chan error, 1)
// Start the HTTP server
go func() {
log.Printf("Starting HTTP server on http://localhost:8080")
log.Printf("SSE Server 1 available at: http://localhost:8080/sse1")
log.Printf("SSE Server 2 available at: http://localhost:8080/sse2")
serverErrors <- srv.ListenAndServe()
}()
// Channel to listen for an interrupt or terminate signal from the OS.
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
// Blocking main and waiting for shutdown.
select {
case err := <-serverErrors:
log.Fatalf("Error starting server: %v", err)
case sig := <-shutdown:
log.Printf("Got signal: %v", sig)
}
// Give outstanding requests a deadline for completion.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Asking listener to shut down and shed load.
if err := srv.Shutdown(ctx); err != nil {
log.Printf("Graceful shutdown did not complete in 10s: %v", err)
if err := srv.Close(); err != nil {
log.Fatalf("Could not stop server: %v", err)
}
}
} file to test against server package main
import (
"context"
"log"
"time"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
func testSSEServer(sseURL string) {
// Create SSE client
c, err := client.NewSSEMCPClient(sseURL)
if err != nil {
log.Printf("Failed to create client for %s: %v", sseURL, err)
return
}
defer c.Close()
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Start the client
if err := c.Start(ctx); err != nil {
log.Printf("Failed to start client for %s: %v", sseURL, err)
return
}
// Initialize the client
initRequest := mcp.InitializeRequest{
Params: struct {
ProtocolVersion string `json:"protocolVersion"`
Capabilities mcp.ClientCapabilities `json:"capabilities"`
ClientInfo mcp.Implementation `json:"clientInfo"`
}{
ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
ClientInfo: mcp.Implementation{
Name: "test-client",
Version: "1.0.0",
},
},
}
initResult, err := c.Initialize(ctx, initRequest)
if err != nil {
log.Printf("Failed to initialize client for %s: %v", sseURL, err)
return
}
log.Printf("\nConnected to server: %s (version %s)", initResult.ServerInfo.Name, initResult.ServerInfo.Version)
// List available tools
toolsRequest := mcp.ListToolsRequest{}
toolsResult, err := c.ListTools(ctx, toolsRequest)
if err != nil {
log.Printf("Failed to list tools for %s: %v", sseURL, err)
return
}
log.Printf("\nAvailable tools for %s:", sseURL)
for i, tool := range toolsResult.Tools {
log.Printf("%d. %s - %s", i+1, tool.Name, tool.Description)
}
// Test calling a session-specific tool
var toolName string
if sseURL == "http://localhost:8080/sse1" {
toolName = "session1-tool"
} else {
toolName = "session2-tool"
}
callRequest := mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments any `json:"arguments,omitempty"`
Meta *mcp.Meta `json:"_meta,omitempty"`
}{
Name: toolName,
},
}
result, err := c.CallTool(ctx, callRequest)
if err != nil {
log.Printf("Failed to call tool for %s: %v", sseURL, err)
return
}
log.Printf("\nTool call result for %s:", sseURL)
if result.IsError {
log.Printf("Error: %v", result.Content)
} else {
for _, content := range result.Content {
if textContent, ok := content.(mcp.TextContent); ok {
log.Printf("Text result: %s", textContent.Text)
} else if imageContent, ok := content.(mcp.ImageContent); ok {
log.Printf("Image result: MIME type: %s, Data length: %d bytes", imageContent.MIMEType, len(imageContent.Data))
} else if audioContent, ok := content.(mcp.AudioContent); ok {
log.Printf("Audio result: MIME type: %s, Data length: %d bytes", audioContent.MIMEType, len(audioContent.Data))
} else if embeddedResource, ok := content.(mcp.EmbeddedResource); ok {
log.Printf("Embedded resource result: Type: %s", embeddedResource.Type)
} else {
log.Printf("Unknown content type: %T", content)
}
}
}
}
func main() {
log.Println("Testing SSE Server 1...")
testSSEServer("http://localhost:8080/sse1")
log.Println("\n----------------------------------------\n")
log.Println("Testing SSE Server 2...")
testSSEServer("http://localhost:8080/sse2")
} |
Beta Was this translation helpful? Give feedback.
0 replies
-
Output Server: 2025/05/28 20:57:08 Starting HTTP server on http://localhost:8080
2025/05/28 20:57:08 SSE Server 1 available at: http://localhost:8080/sse1
2025/05/28 20:57:08 SSE Server 2 available at: http://localhost:8080/sse2
2025/05/28 20:57:38 Session registration hook called for session ID: 7560005c-7d95-4cec-894d-816afd745593
2025/05/28 20:57:38 Server ID from context: sse1
2025/05/28 20:57:38 Session initialized: true
2025/05/28 20:57:38 Successfully added session1-tool for session: 7560005c-7d95-4cec-894d-816afd745593
2025/05/28 20:57:38 Session tools after adding: map[session1-tool:{{session1-tool A tool specific to SSE1 server {object map[] []} [] { 0x1400020808c 0x1400020808d 0x1400020808e 0x1400020808f}} 0x102e29480}]
2025/05/28 20:57:38 Before client initialization for session: 7560005c-7d95-4cec-894d-816afd745593
2025/05/28 20:57:38 After client initialization for session: 7560005c-7d95-4cec-894d-816afd745593
2025/05/28 20:57:38 Client info: {Name:test-client Version:1.0.0}
2025/05/28 20:57:38 Session initialized: true
2025/05/28 20:57:38 Session tools during initialization: map[session1-tool:{{session1-tool A tool specific to SSE1 server {object map[] []} [] { 0x1400020808c 0x1400020808d 0x1400020808e 0x1400020808f}} 0x102e29480}]
2025/05/28 20:57:38 Session registration hook called for session ID: e04a6d11-2f73-4fef-bc5c-342bdd6c3bca
2025/05/28 20:57:38 Server ID from context: sse2
2025/05/28 20:57:38 Session initialized: true
2025/05/28 20:57:38 Successfully added session2-tool for session: e04a6d11-2f73-4fef-bc5c-342bdd6c3bca
2025/05/28 20:57:38 Session tools after adding: map[session2-tool:{{session2-tool A tool specific to SSE2 server {object map[] []} [] { 0x1400009d740 0x1400009d741 0x1400009d742 0x1400009d743}} 0x102e295f0}]
2025/05/28 20:57:38 Before client initialization for session: e04a6d11-2f73-4fef-bc5c-342bdd6c3bca
2025/05/28 20:57:38 After client initialization for session: e04a6d11-2f73-4fef-bc5c-342bdd6c3bca
2025/05/28 20:57:38 Client info: {Name:test-client Version:1.0.0}
2025/05/28 20:57:38 Session initialized: true
2025/05/28 20:57:38 Session tools during initialization: map[session2-tool:{{session2-tool A tool specific to SSE2 server {object map[] []} [] { 0x1400009d740 0x1400009d741 0x1400009d742 0x1400009d743}} 0x102e295f0}] Client: 2025/05/28 20:57:38 Testing SSE Server 1...
2025/05/28 20:57:38
Connected to server: test-server (version 1.0.0)
2025/05/28 20:57:38
Available tools for http://localhost:8080/sse1:
2025/05/28 20:57:38 1. calculator - Perform basic mathematical calculations
2025/05/28 20:57:38 2. session1-tool - A tool specific to SSE1 server
2025/05/28 20:57:38 3. system-info - Get basic system information
2025/05/28 20:57:38 4. text-processor - Process text with various operations like uppercase, lowercase, and word count
2025/05/28 20:57:38
Tool call result for http://localhost:8080/sse1:
2025/05/28 20:57:38 Text result: This is a response from session1-tool
2025/05/28 20:57:38
----------------------------------------
2025/05/28 20:57:38 Testing SSE Server 2...
2025/05/28 20:57:38
Connected to server: test-server (version 1.0.0)
2025/05/28 20:57:38
Available tools for http://localhost:8080/sse2:
2025/05/28 20:57:38 1. calculator - Perform basic mathematical calculations
2025/05/28 20:57:38 2. session2-tool - A tool specific to SSE2 server
2025/05/28 20:57:38 3. system-info - Get basic system information
2025/05/28 20:57:38 4. text-processor - Process text with various operations like uppercase, lowercase, and word count
2025/05/28 20:57:38
Tool call result for http://localhost:8080/sse2:
2025/05/28 20:57:38 Text result: This is a response from session2-tool |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Hi
very new to MCP so please pardon in case this is a dumb question.
we have a situation where we have almost 200 tools to expose from MCP server but a lot of clients only support 40 and most of the user need at max 10 of them. so we were evaluating if it is possible to create multiple SSE servers from a single MCP server with different tool set ?
is this even possible ?
Beta Was this translation helpful? Give feedback.
All reactions