@@ -14,31 +14,46 @@ import (
1414 "fmt"
1515 "io"
1616 "os"
17+ "sync"
1718)
1819
20+ // DefaultProtocolVersion is the MCP protocol version this server speaks.
21+ const DefaultProtocolVersion = "2025-03-26"
22+
1923// Server is an MCP server that communicates over stdio using JSON-RPC 2.0.
2024// It handles the MCP handshake and dispatches tools, resources, and prompts
2125// to registered handlers.
2226type Server struct {
23- name string
24- version string
25- tools map [string ]Tool
26- resources map [string ]Resource
27- prompts map [string ]Prompt
27+ name string
28+ version string
29+ protocolVer string
30+ tools map [string ]Tool
31+ resources map [string ]Resource
32+ prompts map [string ]Prompt
33+ initialized bool
34+ mu sync.Mutex
2835}
2936
3037// NewServer creates a new MCP server with the given name and version.
3138// These are reported to the client during the initialize handshake.
3239func NewServer (name , version string ) * Server {
3340 return & Server {
34- name : name ,
35- version : version ,
36- tools : make (map [string ]Tool ),
37- resources : make (map [string ]Resource ),
38- prompts : make (map [string ]Prompt ),
41+ name : name ,
42+ version : version ,
43+ protocolVer : DefaultProtocolVersion ,
44+ tools : make (map [string ]Tool ),
45+ resources : make (map [string ]Resource ),
46+ prompts : make (map [string ]Prompt ),
3947 }
4048}
4149
50+ // SetProtocolVersion overrides the default MCP protocol version.
51+ func (s * Server ) SetProtocolVersion (v string ) {
52+ s .mu .Lock ()
53+ defer s .mu .Unlock ()
54+ s .protocolVer = v
55+ }
56+
4257// AddTool registers a tool with the server. Tools are callable functions
4358// that the AI client can invoke with arguments.
4459func (s * Server ) AddTool (tool Tool ) {
@@ -60,12 +75,12 @@ func (s *Server) AddPrompt(prompt Prompt) {
6075// Run starts the MCP server using os.Stdin and os.Stdout. It blocks until
6176// stdin closes. Errors are returned if reading or writing fails.
6277func (s * Server ) Run () error {
63- return s .runWithIO (os .Stdin , os .Stdout )
78+ return s .RunWithIO (os .Stdin , os .Stdout )
6479}
6580
66- // runWithIO is the internal entry point accepting arbitrary io.Reader/Writer
67- // for testing with pipes.
68- func (s * Server ) runWithIO (r io.Reader , w io.Writer ) error {
81+ // RunWithIO starts the MCP server with custom I/O readers and writers,
82+ // useful for testing with pipes or buffers .
83+ func (s * Server ) RunWithIO (r io.Reader , w io.Writer ) error {
6984 decoder := json .NewDecoder (r )
7085 encoder := json .NewEncoder (w )
7186
@@ -87,6 +102,17 @@ func (s *Server) runWithIO(r io.Reader, w io.Writer) error {
87102 switch req .Method {
88103 case "initialize" :
89104 respErr = s .handleInitialize (req , encoder )
105+ case "initialized" :
106+ // Notification — silently mark as initialized
107+ s .mu .Lock ()
108+ s .initialized = true
109+ s .mu .Unlock ()
110+ case "ping" :
111+ respErr = encoder .Encode (JSONRPCResponse {
112+ JSONRPC : "2.0" ,
113+ ID : req .ID ,
114+ Result : map [string ]any {},
115+ })
90116 case "tools/list" :
91117 respErr = s .handleToolsList (req , encoder )
92118 case "tools/call" :
@@ -114,11 +140,16 @@ func (s *Server) runWithIO(r io.Reader, w io.Writer) error {
114140
115141// handleInitialize responds to the MCP initialize handshake.
116142func (s * Server ) handleInitialize (req JSONRPCRequest , encoder * json.Encoder ) error {
143+ s .mu .Lock ()
144+ s .initialized = true
145+ ver := s .protocolVer
146+ s .mu .Unlock ()
147+
117148 resp := JSONRPCResponse {
118149 JSONRPC : "2.0" ,
119150 ID : req .ID ,
120151 Result : map [string ]any {
121- "protocolVersion" : "2024-11-05" ,
152+ "protocolVersion" : ver ,
122153 "serverInfo" : map [string ]any {
123154 "name" : s .name ,
124155 "version" : s .version ,
@@ -135,6 +166,13 @@ func (s *Server) handleInitialize(req JSONRPCRequest, encoder *json.Encoder) err
135166
136167// handleToolsList returns metadata for all registered tools.
137168func (s * Server ) handleToolsList (req JSONRPCRequest , encoder * json.Encoder ) error {
169+ s .mu .Lock ()
170+ init := s .initialized
171+ s .mu .Unlock ()
172+ if ! init {
173+ return encoder .Encode (NewJSONRPCError (req .ID , - 32600 , "Not initialized" ))
174+ }
175+
138176 toolList := make ([]Tool , 0 , len (s .tools ))
139177 for _ , tool := range s .tools {
140178 toolList = append (toolList , tool )
@@ -157,6 +195,13 @@ type toolsCallParams struct {
157195
158196// handleToolsCall dispatches a tool call to the registered handler.
159197func (s * Server ) handleToolsCall (req JSONRPCRequest , encoder * json.Encoder ) error {
198+ s .mu .Lock ()
199+ init := s .initialized
200+ s .mu .Unlock ()
201+ if ! init {
202+ return encoder .Encode (NewJSONRPCError (req .ID , - 32600 , "Not initialized" ))
203+ }
204+
160205 var params toolsCallParams
161206 if err := json .Unmarshal (req .Params , & params ); err != nil {
162207 errResp := NewJSONRPCError (req .ID , - 32602 , "Invalid params" )
@@ -165,15 +210,35 @@ func (s *Server) handleToolsCall(req JSONRPCRequest, encoder *json.Encoder) erro
165210
166211 tool , ok := s .tools [params .Name ]
167212 if ! ok {
168- errResp := NewJSONRPCError (req .ID , - 32602 , fmt .Sprintf ("Unknown tool: %s" , params .Name ))
169- return encoder .Encode (errResp )
213+ // Return in-band error per MCP convention
214+ resp := JSONRPCResponse {
215+ JSONRPC : "2.0" ,
216+ ID : req .ID ,
217+ Result : map [string ]any {
218+ "content" : []map [string ]any {
219+ {"type" : "text" , "text" : fmt .Sprintf ("Unknown tool: %s" , params .Name )},
220+ },
221+ "isError" : true ,
222+ },
223+ }
224+ return encoder .Encode (resp )
170225 }
171226
172227 ctx := context .Background ()
173228 result , err := tool .Handler (ctx , params .Arguments )
174229 if err != nil {
175- errResp := NewJSONRPCError (req .ID , - 32000 , err .Error ())
176- return encoder .Encode (errResp )
230+ // Return in-band error per MCP convention
231+ resp := JSONRPCResponse {
232+ JSONRPC : "2.0" ,
233+ ID : req .ID ,
234+ Result : map [string ]any {
235+ "content" : []map [string ]any {
236+ {"type" : "text" , "text" : fmt .Sprintf ("Error: %v" , err )},
237+ },
238+ "isError" : true ,
239+ },
240+ }
241+ return encoder .Encode (resp )
177242 }
178243
179244 resp := JSONRPCResponse {
0 commit comments