s := server.NewMCPServer("ip-mcp","1.0.0",)
tool := mcp.NewTool("ip_query",mcp.WithDescription("query geo location of an IP address"),mcp.WithString("ip",mcp.Required(),mcp.Description("IP address to query"),),)
s.AddTool(tool, ipQueryHandler)
if err := server.ServeStdio(s); err != nil {
func NewMCPServer(name, version string,opts ...ServerOption,) *MCPServer {s := &MCPServer{resources: make(map[string]resourceEntry),resourceTemplates: make(map[string]resourceTemplateEntry),prompts: make(map[string]mcp.Prompt),promptHandlers: make(map[string]PromptHandlerFunc),tools: make(map[string]ServerTool),name: name,version: version,notificationHandlers: make(map[string]NotificationHandlerFunc),capabilities: serverCapabilities{tools: nil,resources: nil,prompts: nil,logging: false,},}for _, opt := range opts {opt(s)}return s}
type MCPServer struct {// Separate mutexes for different resource typesresourcesMu sync.RWMutexpromptsMu sync.RWMutextoolsMu sync.RWMutexmiddlewareMu sync.RWMutexnotificationHandlersMu sync.RWMutexcapabilitiesMu sync.RWMutextoolFiltersMu sync.RWMutexname stringversion stringinstructions stringresources map[string]resourceEntryresourceTemplates map[string]resourceTemplateEntryprompts map[string]mcp.PromptpromptHandlers map[string]PromptHandlerFunctools map[string]ServerTooltoolHandlerMiddlewares []ToolHandlerMiddlewaretoolFilters []ToolFilterFuncnotificationHandlers map[string]NotificationHandlerFunccapabilities serverCapabilitiespaginationLimit *intsessions sync.Maphooks *Hooks}
func NewTool(name string, opts ...ToolOption) Tool {tool := Tool{Name: name,InputSchema: ToolInputSchema{Type: "object",Properties: make(map[string]any),Required: nil, // Will be omitted from JSON if empty},Annotations: ToolAnnotation{Title: "",ReadOnlyHint: ToBoolPtr(false),DestructiveHint: ToBoolPtr(true),IdempotentHint: ToBoolPtr(false),OpenWorldHint: ToBoolPtr(true),},}
type Tool struct {// The name of the tool.Name string `json:"name"`// A human-readable description of the tool.Description string `json:"description,omitempty"`// A JSON Schema object defining the expected parameters for the tool.InputSchema ToolInputSchema `json:"inputSchema"`// Alternative to InputSchema - allows arbitrary JSON Schema to be providedRawInputSchema json.RawMessage `json:"-"` // Hide this from JSON marshaling// Optional properties describing tool behaviorAnnotations ToolAnnotation `json:"annotations"`}
type ToolInputSchema struct {Type string `json:"type"`Properties map[string]any `json:"properties,omitempty"`Required []string `json:"required,omitempty"`}
type ToolAnnotation struct {// Human-readable title for the toolTitle string `json:"title,omitempty"`// If true, the tool does not modify its environmentReadOnlyHint *bool `json:"readOnlyHint,omitempty"`// If true, the tool may perform destructive updatesDestructiveHint *bool `json:"destructiveHint,omitempty"`// If true, repeated calls with same args have no additional effectIdempotentHint *bool `json:"idempotentHint,omitempty"`// If true, tool interacts with external entitiesOpenWorldHint *bool `json:"openWorldHint,omitempty"`}
func (s *MCPServer) AddTool(tool mcp.Tool, handler ToolHandlerFunc) {s.AddTools(ServerTool{Tool: tool, Handler: handler})}
type ToolHandlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
type CallToolRequest struct {RequestParams struct {Name string `json:"name"`Arguments map[string]any `json:"arguments,omitempty"`Meta *struct {// If specified, the caller is requesting out-of-band progress// notifications for this request (as represented by// notifications/progress). The value of this parameter is an// opaque token that will be attached to any subsequent// notifications. The receiver is not obligated to provide these// notifications.ProgressToken ProgressToken `json:"progressToken,omitempty"`} `json:"_meta,omitempty"`} `json:"params"`}
type Request struct {Method string `json:"method"`Params struct {Meta *struct {// If specified, the caller is requesting out-of-band progress// notifications for this request (as represented by// notifications/progress). The value of this parameter is an// opaque token that will be attached to any subsequent// notifications. The receiver is not obligated to provide these// notifications.ProgressToken ProgressToken `json:"progressToken,omitempty"`} `json:"_meta,omitempty"`} `json:"params,omitempty"`}
type CallToolResult struct {ResultContent []Content `json:"content"` // Can be TextContent, ImageContent, AudioContent, or EmbeddedResource// Whether the tool call ended in an error.//// If not set, this is assumed to be false (the call was successful).IsError bool `json:"isError,omitempty"`}
type Result struct {// This result property is reserved by the protocol to allow clients and// servers to attach additional metadata to their responses.Meta map[string]any `json:"_meta,omitempty"`}
func (s *MCPServer) AddTools(tools ...ServerTool) {s.toolsMu.Lock()for _, entry := range tools {s.tools[entry.Tool.Name] = entry}s.toolsMu.Unlock()}
func ServeStdio(server *MCPServer, opts ...StdioOption) error {s := NewStdioServer(server)return s.Listen(ctx, os.Stdin, os.Stdout)
func NewStdioServer(server *MCPServer) *StdioServer {return &StdioServer{server: server,errLogger: log.New(os.Stderr,"",log.LstdFlags,), // Default to discarding logs}}
func (s *StdioServer) Listen(ctx context.Context,stdin io.Reader,stdout io.Writer,) error {// Set a static client context since stdio only has one clientif err := s.server.RegisterSession(ctx, &stdioSessionInstance); err != nil {return fmt.Errorf("register session: %w", err)}defer s.server.UnregisterSession(ctx, stdioSessionInstance.SessionID())reader := bufio.NewReader(stdin)go s.handleNotifications(ctx, stdout)return s.processInputStream(ctx, reader, stdout)
var stdioSessionInstance = stdioSession{notifications: make(chan mcp.JSONRPCNotification, 100),}
func (s *stdioSession) SessionID() string {return "stdio"}
func (s *StdioServer) handleNotifications(ctx context.Context, stdout io.Writer) {for {select {case notification := <-stdioSessionInstance.notifications:if err := s.writeResponse(notification, stdout); err != nil {
func (s *StdioServer) writeResponse(response mcp.JSONRPCMessage,writer io.Writer,) error {responseBytes, err := json.Marshal(response)if err != nil {return err}// Write response followed by newlineif _, err := fmt.Fprintf(writer, "%s\n", responseBytes); err != nil {
func (s *StdioServer) processInputStream(ctx context.Context, reader *bufio.Reader, stdout io.Writer) error {for {if err := ctx.Err(); err != nil {return err}line, err := s.readNextLine(ctx, reader)if err != nil {if err == io.EOF {return nil}s.errLogger.Printf("Error reading input: %v", err)return err}if err := s.processMessage(ctx, line, stdout); err != nil {
func (s *StdioServer) processMessage(ctx context.Context,line string,writer io.Writer,) error {// Parse the message as raw JSONvar rawMessage json.RawMessageif err := json.Unmarshal([]byte(line), &rawMessage); err != nil {response := createErrorResponse(nil, mcp.PARSE_ERROR, "Parse error")return s.writeResponse(response, writer)}// Handle the message using the wrapped serverresponse := s.server.HandleMessage(ctx, rawMessage)// Only write response if there is one (not for notifications)if response != nil {if err := s.writeResponse(response, writer); err != nil {
func (s *MCPServer) HandleMessage(ctx context.Context,message json.RawMessage,) mcp.JSONRPCMessage {// Add server to contextctx = context.WithValue(ctx, serverKey{}, s)var err *requestErrorvar baseMessage struct {JSONRPC string `json:"jsonrpc"`Method mcp.MCPMethod `json:"method"`ID any `json:"id,omitempty"`Result any `json:"result,omitempty"`}if err := json.Unmarshal(message, &baseMessage); err != nil {return createErrorResponse(nil,mcp.PARSE_ERROR,"Failed to parse message",)}handleErr := s.hooks.onRequestInitialization(ctx, baseMessage.ID, message)switch baseMessage.Method {case mcp.MethodInitialize:s.hooks.beforeInitialize(ctx, baseMessage.ID, &request)result, err = s.handleInitialize(ctx, baseMessage.ID, request)s.hooks.afterInitialize(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodPing:s.hooks.beforePing(ctx, baseMessage.ID, &request)result, err = s.handlePing(ctx, baseMessage.ID, request)s.hooks.afterPing(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodResourcesList:s.hooks.beforeListResources(ctx, baseMessage.ID, &request)result, err = s.handleListResources(ctx, baseMessage.ID, request)s.hooks.afterListResources(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodResourcesTemplatesList:s.hooks.beforeListResourceTemplates(ctx, baseMessage.ID, &request)result, err = s.handleListResourceTemplates(ctx, baseMessage.ID, request)s.hooks.afterListResourceTemplates(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodResourcesRead:s.hooks.beforeReadResource(ctx, baseMessage.ID, &request)result, err = s.handleReadResource(ctx, baseMessage.ID, request)s.hooks.afterReadResource(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodPromptsList:s.hooks.beforeListPrompts(ctx, baseMessage.ID, &request)result, err = s.handleListPrompts(ctx, baseMessage.ID, request)s.hooks.afterListPrompts(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodPromptsGet:s.hooks.beforeGetPrompt(ctx, baseMessage.ID, &request)result, err = s.handleGetPrompt(ctx, baseMessage.ID, request)s.hooks.afterGetPrompt(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodToolsList:s.hooks.beforeListTools(ctx, baseMessage.ID, &request)result, err = s.handleListTools(ctx, baseMessage.ID, request)s.hooks.afterListTools(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)case mcp.MethodToolsCall:s.hooks.beforeCallTool(ctx, baseMessage.ID, &request)result, err = s.handleToolCall(ctx, baseMessage.ID, request)s.hooks.afterCallTool(ctx, baseMessage.ID, &request, result)return createResponse(baseMessage.ID, *result)
func (s *MCPServer) handleListTools(ctx context.Context,id any,request mcp.ListToolsRequest,) (*mcp.ListToolsResult, *requestError) {for name := range s.tools {toolNames = append(toolNames, name)}// Sort the tool names for consistent orderingsort.Strings(toolNames)// Add tools in sorted orderfor _, name := range toolNames {tools = append(tools, s.tools[name].Tool)}toolMap := make(map[string]mcp.Tool)// Add global tools firstfor _, tool := range tools {toolMap[tool.Name] = tool}// Then override with session-specific toolsfor name, serverTool := range sessionTools {toolMap[name] = serverTool.Tool}// Convert back to slicetools = make([]mcp.Tool, 0, len(toolMap))for _, tool := range toolMap {tools = append(tools, tool)}
func (s *MCPServer) handleToolCall(ctx context.Context,id any,request mcp.CallToolRequest,) (*mcp.CallToolResult, *requestError) {session := ClientSessionFromContext(ctx)if session != nil {if sessionWithTools, typeAssertOk := session.(SessionWithTools); typeAssertOk {if sessionTools := sessionWithTools.GetSessionTools(); sessionTools != nil {var sessionOk booltool, sessionOk = sessionTools[request.Params.Name]if !ok {s.toolsMu.RLock()tool, ok = s.tools[request.Params.Name]s.toolsMu.RUnlock()}finalHandler := tool.Handlers.middlewareMu.RLock()mw := s.toolHandlerMiddlewaress.middlewareMu.RUnlock()// Apply middlewares in reverse orderfor i := len(mw) - 1; i >= 0; i-- {finalHandler = mw[i](finalHandler)}result, err := finalHandler(ctx, request)


文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




