server

package
v0.0.28 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package server is a drop-in compatibility shim for github.com/mark3labs/mcp-go/server, reimplemented on top of the official github.com/modelcontextprotocol/go-sdk.

It presents mcp-go's MCPServer API (NewMCPServer, AddTool, AddResource, AddPrompt, the ServerTool/ServerResource/ServerPrompt registration units, the Hooks and per-session interfaces, and the stdio/SSE/Streamable-HTTP transports) while delegating protocol handling to a go-sdk Server. Tools, resources and prompts registered here are converted to their go-sdk equivalents and served by the SDK.

Scope and status

The global registration path (AddTool/AddResource/AddPrompt served over the stdio and HTTP transports) is fully functional and tested. The per-session interfaces (SessionWithTools, SessionWithResources, SessionWithElicitation, SessionIdManager) and the Hooks type are implemented and wired: per-session tool/resource overlays set via SetSessionTools/SetSessionResources are reconciled onto the session's live go-sdk server (syncSessionTools/ syncSessionResources), and the before-list/before-call hooks fire ahead of SDK dispatch so ToolHive's lazy per-session tool injection runs first. Cross-replica session rehydration (Validate-driven lazy eviction) and the Streamable HTTP transports are functional. See the notes on SessionWithTools for the live-overlay reconciliation details.

Stability: Alpha.

Index

Constants

This section is empty.

Variables

View Source
var ErrElicitationNotSupported = errors.New("session does not support elicitation")

ErrElicitationNotSupported is returned when the session in scope does not support elicitation (it does not implement SessionWithElicitation, or its bound go-sdk ServerSession is unavailable). It mirrors mcp-go's server.ErrElicitationNotSupported.

View Source
var ErrNoActiveSession = errors.New("no active session")

ErrNoActiveSession is returned when an elicitation is requested outside a session's request context (no ClientSession in ctx). It mirrors mcp-go's server.ErrNoActiveSession.

Functions

func ServeStdio

func ServeStdio(server *MCPServer, _ ...StdioOption) error

ServeStdio runs the MCP server over stdio until the context is done. It mirrors mcp-go's server.ServeStdio.

Types

type ClientSession

type ClientSession interface {
	// Initialize marks the session as fully initialized.
	Initialize()
	// Initialized reports whether the session is ready for notifications.
	Initialized() bool
	// NotificationChannel provides a channel for sending notifications to the client.
	NotificationChannel() chan<- mcp.JSONRPCNotification
	// SessionID uniquely identifies the session.
	SessionID() string
}

ClientSession represents an active client session. It mirrors mcp-go's server.ClientSession.

func ClientSessionFromContext

func ClientSessionFromContext(ctx context.Context) ClientSession

ClientSessionFromContext returns the ClientSession stored in ctx, or nil.

type HTTPContextFunc

type HTTPContextFunc func(ctx context.Context, r *http.Request) context.Context

HTTPContextFunc customizes the request context for HTTP transports.

type Hooks

type Hooks struct {
	// contains filtered or unexported fields
}

Hooks holds lifecycle callbacks. It mirrors the subset of mcp-go's server.Hooks that ToolHive registers.

func (*Hooks) AddBeforeCallTool

func (c *Hooks) AddBeforeCallTool(hook OnBeforeCallToolFunc)

AddBeforeCallTool registers a before-tools/call hook.

func (*Hooks) AddBeforeListTools

func (c *Hooks) AddBeforeListTools(hook OnBeforeListToolsFunc)

AddBeforeListTools registers a before-tools/list hook.

func (*Hooks) AddOnRegisterSession

func (c *Hooks) AddOnRegisterSession(hook OnRegisterSessionHookFunc)

AddOnRegisterSession registers a session-registration hook.

type MCPServer

type MCPServer struct {
	// contains filtered or unexported fields
}

MCPServer is an MCP server backed by the official go-sdk. It mirrors the subset of mcp-go's server.MCPServer that ToolHive uses.

func NewMCPServer

func NewMCPServer(name, version string, opts ...ServerOption) *MCPServer

NewMCPServer creates a new MCP server with the given name and version.

func (*MCPServer) AddPrompt

func (s *MCPServer) AddPrompt(prompt mcp.Prompt, handler PromptHandlerFunc)

AddPrompt registers a prompt and its handler.

func (*MCPServer) AddResource

func (s *MCPServer) AddResource(resource mcp.Resource, handler ResourceHandlerFunc)

AddResource registers a resource and its handler.

func (*MCPServer) AddResourceTemplate

func (s *MCPServer) AddResourceTemplate(template mcp.ResourceTemplate, handler ResourceTemplateHandlerFunc)

AddResourceTemplate registers a resource template and its handler.

func (*MCPServer) AddTool

func (s *MCPServer) AddTool(tool mcp.Tool, handler ToolHandlerFunc)

AddTool registers a tool and its handler.

func (*MCPServer) AddTools

func (s *MCPServer) AddTools(tools ...ServerTool)

AddTools registers multiple tools.

func (*MCPServer) DeleteTools

func (s *MCPServer) DeleteTools(names ...string)

DeleteTools removes tools by name.

func (*MCPServer) HandleMessage

func (s *MCPServer) HandleMessage(ctx context.Context, message json.RawMessage) mcp.JSONRPCMessage

HandleMessage processes a single incoming JSON-RPC message and returns the appropriate JSON-RPC response (or nil for notifications and server-directed responses). It mirrors mcp-go's server.MCPServer.HandleMessage.

go-sdk backing and limitation: the go-sdk drives its own JSON-RPC loop over a Transport and does not expose a public "handle one raw message" entrypoint. This shim therefore dispatches the message directly against the tools, resources, prompts and their handlers registered on this MCPServer — the same registration state the go-sdk server is built from — so behavior matches mcp-go for the methods ToolHive exercises: initialize, ping, tools/list, tools/call, resources/list, resources/templates/list, resources/read, prompts/list and prompts/get, plus notifications (which return nil). Capability-gated extras that ToolHive does not use over this path (logging setLevel, subscribe/unsubscribe, completion, tasks) return METHOD_NOT_FOUND.

func (*MCPServer) RequestElicitation

func (*MCPServer) RequestElicitation(ctx context.Context, request mcp.ElicitationRequest) (*mcp.ElicitationResult, error)

RequestElicitation sends an elicitation/create request to the client bound to the session in ctx (the session a tool/prompt/resource handler is running under) and returns the user's response. The client must have declared the elicitation capability during initialization; otherwise the go-sdk rejects the call with "client does not support elicitation".

Under the shim's Streamable HTTP transport the server replies with application/json (JSONResponse is on by design so ToolHive callers that json.Decode the body keep working). The go-sdk therefore routes a server->client elicitation request made during request handling to the standalone SSE stream (the "" stream) rather than the request's response. The client MUST hold an open standalone SSE stream for the elicitation to be delivered and answered: configure the shim client with transport.WithContinuousListening() (the default is DisableStandaloneSSE: true, under which elicitation cannot complete). See the build() doc comment in transports.go.

It mirrors mcp-go's server.MCPServer.RequestElicitation.

func (*MCPServer) SendNotificationToAllClients

func (s *MCPServer) SendNotificationToAllClients(method string, params map[string]any)

SendNotificationToAllClients broadcasts a server-initiated notification with the given method and params to every currently connected client session. It mirrors mcp-go's server.MCPServer.SendNotificationToAllClients, which ToolHive's stdio bridge uses to relay upstream notifications downstream.

go-sdk backing and limitation: the go-sdk does not expose a public API to send an arbitrary (method, params) notification on a ServerSession; only typed senders are exported. This method therefore maps the well-known MCP notification methods onto the go-sdk's typed senders:

  • notifications/progress -> ServerSession.NotifyProgress
  • notifications/message -> ServerSession.Log (delivered only once the client has set a logging level, per the go-sdk/spec behavior)

The list-changed notifications (tools/prompts/resources) are NOT re-broadcast through this method: the go-sdk server emits them automatically to connected clients whenever its registered feature set changes on a live session (per-session srv.AddTool / srv.RemoveTools, as driven by syncSessionTools when WithToolCapabilities(true) is set), so a manual broadcast would double them. Any list-changed method (and any other unrecognized method) is dropped here rather than forwarded. This is the one behavioral gap versus mcp-go's raw channel-based broadcast and is documented rather than silently ignored.

func (*MCPServer) SetTools

func (s *MCPServer) SetTools(tools ...ServerTool)

SetTools replaces the tool set.

func (*MCPServer) WithContext

func (*MCPServer) WithContext(ctx context.Context, session ClientSession) context.Context

WithContext stores the given ClientSession in the returned context so that handlers (and ClientSessionFromContext) can recover it. It mirrors mcp-go's server.MCPServer.WithContext, which ToolHive uses to associate a session with a request context (e.g. for per-session tool injection).

type NotificationHandlerFunc

type NotificationHandlerFunc func(ctx context.Context, notification mcp.JSONRPCNotification)

NotificationHandlerFunc handles a client notification.

type OnBeforeCallToolFunc

type OnBeforeCallToolFunc func(ctx context.Context, id any, message *mcp.CallToolRequest)

OnBeforeCallToolFunc runs before a tools/call request is handled.

type OnBeforeListToolsFunc

type OnBeforeListToolsFunc func(ctx context.Context, id any, message *mcp.ListToolsRequest)

OnBeforeListToolsFunc runs before a tools/list request is handled.

type OnRegisterSessionHookFunc

type OnRegisterSessionHookFunc func(ctx context.Context, session ClientSession)

OnRegisterSessionHookFunc runs when a session registers.

type PromptHandlerFunc

type PromptHandlerFunc func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error)

PromptHandlerFunc handles a prompt get.

type ResourceHandlerFunc

type ResourceHandlerFunc func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error)

ResourceHandlerFunc handles a resource read.

type ResourceTemplateHandlerFunc

type ResourceTemplateHandlerFunc func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error)

ResourceTemplateHandlerFunc handles a templated resource read.

type SSEOption

type SSEOption func(*SSEServer)

SSEOption configures an SSEServer.

func WithMessageEndpoint

func WithMessageEndpoint(endpoint string) SSEOption

WithMessageEndpoint sets the message endpoint path.

func WithSSEEndpoint

func WithSSEEndpoint(endpoint string) SSEOption

WithSSEEndpoint sets the SSE endpoint path.

type SSEServer

type SSEServer struct {
	// contains filtered or unexported fields
}

SSEServer serves the MCP server over the (legacy) SSE transport.

func NewSSEServer

func NewSSEServer(server *MCPServer, opts ...SSEOption) *SSEServer

NewSSEServer creates an SSE server for the MCP server.

func (*SSEServer) MessageHandler

func (s *SSEServer) MessageHandler() http.Handler

MessageHandler returns the http.Handler for the message (POST) endpoint. See SSEHandler for the go-sdk backing and the shared-handler limitation.

func (*SSEServer) SSEHandler

func (s *SSEServer) SSEHandler() http.Handler

SSEHandler returns the http.Handler for the SSE (stream) endpoint. It mirrors mcp-go's SSEServer.SSEHandler, allowing the endpoint to be mounted on a custom router.

go-sdk backing and limitation: the go-sdk serves SSE and message delivery through a single unified handler keyed off the request method and a session query parameter, whereas mcp-go splits them across two paths. Both SSEHandler and MessageHandler therefore return the same underlying go-sdk handler; when mounting them on separate paths, mount both under a common base path so the go-sdk handler can correlate the stream and its message posts.

func (*SSEServer) ServeHTTP

func (s *SSEServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

func (*SSEServer) Shutdown

func (s *SSEServer) Shutdown(ctx context.Context) error

Shutdown gracefully stops the HTTP server.

func (*SSEServer) Start

func (s *SSEServer) Start(addr string) error

Start serves on addr until Shutdown is called.

type ServerOption

type ServerOption func(*MCPServer)

ServerOption configures an MCPServer.

func WithHooks

func WithHooks(hooks *Hooks) ServerOption

WithHooks installs lifecycle hooks.

func WithLogger

func WithLogger(logger *slog.Logger) ServerOption

WithLogger sets the server logger.

func WithLogging

func WithLogging() ServerOption

WithLogging enables logging capability.

func WithPageSize added in v0.0.28

func WithPageSize(n int) ServerOption

WithPageSize configures the server's list pagination page size (the maximum number of items returned in a single tools/list, resources/list, or prompts/list response). A value of 0 leaves go-sdk's default (DefaultPageSize=1000) in place. mcp-go returned all items in one page; aggregators with more than 1000 tools must raise this to avoid pagination.

func WithPromptCapabilities

func WithPromptCapabilities(listChanged bool) ServerOption

WithPromptCapabilities declares prompt support.

Invoking this option advertises the prompts capability in the initialize result regardless of whether any prompts are registered at initialize time (see WithToolCapabilities for the per-session registration rationale).

func WithResourceCapabilities

func WithResourceCapabilities(subscribe, listChanged bool) ServerOption

WithResourceCapabilities declares resource support.

Invoking this option advertises the resources capability in the initialize result regardless of whether any resources are registered at initialize time (see WithToolCapabilities for the per-session registration rationale).

func WithToolCapabilities

func WithToolCapabilities(listChanged bool) ServerOption

WithToolCapabilities declares tool support (listChanged notifications).

Invoking this option advertises the tools capability in the initialize result regardless of whether any tools are registered at initialize time: vMCP registers tools per-session AFTER initialize, so without this the capability would be absent (go-sdk otherwise infers capabilities from registered features).

type ServerPrompt

type ServerPrompt struct {
	Prompt  mcp.Prompt
	Handler PromptHandlerFunc
}

ServerPrompt pairs a prompt with its handler.

type ServerResource

type ServerResource struct {
	Resource mcp.Resource
	Handler  ResourceHandlerFunc
}

ServerResource pairs a resource with its handler.

type ServerResourceTemplate

type ServerResourceTemplate struct {
	Template mcp.ResourceTemplate
	Handler  ResourceTemplateHandlerFunc
}

ServerResourceTemplate pairs a resource template with its handler.

type ServerTool

type ServerTool struct {
	Tool    mcp.Tool
	Handler ToolHandlerFunc
}

ServerTool pairs a tool with its handler.

type SessionIdManager

type SessionIdManager interface {
	// Generate returns a new session ID.
	Generate() string
	// Validate reports whether a session ID is valid; isTerminated is true if
	// the ID is valid but belongs to a terminated session.
	Validate(sessionID string) (isTerminated bool, err error)
	// Terminate marks a session ID terminated; isNotAllowed is true if policy
	// prevents client termination.
	Terminate(sessionID string) (isNotAllowed bool, err error)
}

SessionIdManager governs MCP session ID lifecycle. It mirrors mcp-go's server.SessionIdManager so ToolHive's implementation can be supplied via WithSessionIdManager.

type SessionWithElicitation added in v0.0.28

type SessionWithElicitation interface {
	ClientSession
	RequestElicitation(ctx context.Context, request mcp.ElicitationRequest) (*mcp.ElicitationResult, error)
}

SessionWithElicitation is a ClientSession that can send elicitation requests to the client. It mirrors mcp-go's server.SessionWithElicitation so callers that type-assert against it (as mcp-go's RequestElicitation does) keep working; the shim's clientSession implements it.

type SessionWithResources

type SessionWithResources interface {
	ClientSession
	// GetSessionResources returns the session's resources. Thread-safe.
	GetSessionResources() map[string]ServerResource
	// SetSessionResources sets the session's resources. Thread-safe.
	SetSessionResources(resources map[string]ServerResource)
}

SessionWithResources is a ClientSession that carries per-session resources.

type SessionWithTools

type SessionWithTools interface {
	ClientSession
	// GetSessionTools returns the session's tools. Thread-safe.
	GetSessionTools() map[string]ServerTool
	// SetSessionTools sets the session's tools. Thread-safe.
	SetSessionTools(tools map[string]ServerTool)
}

SessionWithTools is a ClientSession that carries per-session tools. ToolHive's vMCP layer uses this to project a per-session tool set.

Overlays set here are stored and reconciled onto the session's live go-sdk server (syncSessionTools) once a server is bound to the session (at registration, or immediately if already bound): added tools are registered via srv.AddTool and removed tools via srv.RemoveTools, which also drives go-sdk's automatic notifications/tools/list_changed emission when WithToolCapabilities(true) is set. So per-session tool changes take effect on an already-connected session at runtime.

type StdioOption

type StdioOption func(*stdioConfig)

StdioOption configures the stdio server (retained for API compatibility).

type StreamableHTTPOption

type StreamableHTTPOption func(*StreamableHTTPServer)

StreamableHTTPOption configures a StreamableHTTPServer.

func WithDisableLocalhostProtection added in v0.0.28

func WithDisableLocalhostProtection(disable bool) StreamableHTTPOption

WithDisableLocalhostProtection disables go-sdk's DNS-rebinding/localhost protection on the Streamable HTTP transport. go-sdk by default rejects (403) requests that arrive on a loopback listener but carry a non-localhost Host header; mcp-go had no such protection, so local proxies that set a custom Host header must disable it. Pass true to disable the protection (restoring mcp-go's behavior); pass false (or omit) to leave go-sdk's default (protected) in place.

func WithEndpointPath

func WithEndpointPath(endpointPath string) StreamableHTTPOption

WithEndpointPath sets the HTTP path the server is mounted at.

func WithHTTPContextFunc

func WithHTTPContextFunc(fn HTTPContextFunc) StreamableHTTPOption

WithHTTPContextFunc installs a per-request context customizer.

Context values injected here are applied to ALL POSTs, including the initialize request (previously the nonce bridge only bridged non-initialize POSTs; the contextFunc itself now runs unconditionally on every request). This is harmless/desired but observable: an initialize handler that reads a context value populated by contextFunc will now see it.

func WithHeartbeatInterval

func WithHeartbeatInterval(interval time.Duration) StreamableHTTPOption

WithHeartbeatInterval sets the keep-alive ping interval. This option is currently a NO-OP: it stores the interval but does NOT wire it to go-sdk's ServerOptions.KeepAlive. go-sdk's KeepAlive sends an active ping REQUEST (server→client) each interval and session.Close()s on failure; under JSONResponse mode that ping routes to the standalone SSE stream, and a JSON-only client (no GET stream, the shim client's own default) has that stream unconnected → ping rejected → session closed on the first tick. That would evict every healthy JSON-only client session at the interval. mcp-go's heartbeat was passive (pings written only to an existing GET stream, never awaited, never terminating).

NOTE: the passive keep-alive is more load-bearing than "nice to have" now that elicitation and all server→client notifications (list_changed, progress, logging, resources/updated, elicitation/complete) structurally depend on the client's idle standalone SSE stream staying open. Any intermediary (LB, reverse proxy, ToolHive's own proxy) will reap that idle stream on timeout — after which elicitation fails (ErrRejected) and server-initiated notifications are silently dropped, with no reconnect. It also feeds unbounded session growth (abandoned sessions are never reaped).

TODO(issue #156): implement a passive keep-alive (SSE comment injection via a ResponseWriter wrapper around the go-sdk handler) matching mcp-go's design and wire this option to it. This should be prioritized before internet-facing vMCP use; a max-session cap / idle sweep would also help. Until then the value is stored but unused.

func WithSessionIdManager

func WithSessionIdManager(manager SessionIdManager) StreamableHTTPOption

WithSessionIdManager supplies a session ID manager that drives cross-replica session lifecycle.

The manager IS wired into the SDK's session lifecycle:

  • Generate is installed as the go-sdk Server's GetSessionID (buildServer), so the SDK mints session IDs through it — this is where ToolHive's manager creates the placeholder session record that OnRegisterSession later promotes.
  • Validate is called on EVERY request carrying a session ID: for local sessions (initialized on this instance) it is validated per-request so a session terminated in the shared store (e.g. via a DELETE on another replica) is rejected here and its local bookkeeping dropped; for cross-replica sessions it drives lazy eviction on each request.
  • Terminate is forwarded the session ID on a DELETE so ToolHive's shared session storage is cleaned up in lockstep with the local session.

The single-replica fast path (no manager) is preserved: there is nothing to validate against, so local sessions route straight through to the go-sdk handler.

type StreamableHTTPServer

type StreamableHTTPServer struct {
	// contains filtered or unexported fields
}

StreamableHTTPServer serves the MCP server over the Streamable HTTP transport. It implements http.Handler so it can be mounted on an http.ServeMux, and also offers Start/Shutdown for standalone use.

func NewStreamableHTTPServer

func NewStreamableHTTPServer(server *MCPServer, opts ...StreamableHTTPOption) *StreamableHTTPServer

NewStreamableHTTPServer creates a Streamable HTTP server for the MCP server.

func (*StreamableHTTPServer) ServeHTTP

func (s *StreamableHTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

func (*StreamableHTTPServer) Shutdown

func (s *StreamableHTTPServer) Shutdown(ctx context.Context) error

Shutdown gracefully stops the HTTP server.

func (*StreamableHTTPServer) Start

func (s *StreamableHTTPServer) Start(addr string) error

Start serves on addr until Shutdown is called.

type ToolHandlerFunc

type ToolHandlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)

ToolHandlerFunc handles a tool call. It mirrors mcp-go's type exactly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL