@@ -29,8 +29,9 @@ public abstract class MCPApplication extends AbstractApplication {
2929 protected JsonRpcHandler jsonRpcHandler ;
3030 protected AuthorizationHandler authHandler ;
3131
32- protected SessionState sessionState = SessionState . DISCONNECTED ;
32+ protected final Map < String , SessionState > sessionStates = new ConcurrentHashMap <>() ;
3333 protected final Map <String , Object > sessionMap = new ConcurrentHashMap <>(); // sessionId -> user info or state
34+ private static final ThreadLocal <String > currentSessionId = new ThreadLocal <>();
3435
3536 // Generic registries for tools, resources, prompts, and custom RPC handlers
3637 protected final Map <String , MCPTool > tools = new java .util .concurrent .ConcurrentHashMap <>();
@@ -40,8 +41,10 @@ public abstract class MCPApplication extends AbstractApplication {
4041 protected static final Map <String , MCPTool .MCPToolMethod > toolMethods = new java .util .concurrent .ConcurrentHashMap <>();
4142
4243 /**
43- * Initializes the MCP application, setting up authentication, SSE, JSON-RPC handler,
44- * and registering core protocol handlers. Subclasses should call super.init() and
44+ * Initializes the MCP application, setting up authentication, SSE, JSON-RPC
45+ * handler,
46+ * and registering core protocol handlers. Subclasses should call super.init()
47+ * and
4548 * may register additional handlers for custom protocol extensions.
4649 */
4750 @ Override
@@ -68,8 +71,8 @@ public void init() {
6871 this .registerRpcHandler (Methods .SHUTDOWN , (req , res , app ) -> app .handleShutdown (req , res ));
6972 this .registerRpcHandler (Methods .GET_STATUS , (req , res , app ) -> app .handleGetStatus (req , res ));
7073 this .registerRpcHandler (Methods .INITIALIZED_NOTIFICATION , (req , res , app ) -> {
71- if (app .sessionState == SessionState .INITIALIZING ) {
72- app .sessionState = SessionState .READY ;
74+ if (app .getSessionState () == SessionState .INITIALIZING ) {
75+ app .setSessionState ( SessionState .READY ) ;
7376 res .setResult (null );
7477 } else {
7578 res .setError (new JsonRpcError (ErrorCodes .INVALID_REQUEST , "Not in initializing state" ));
@@ -81,9 +84,9 @@ public void init() {
8184 if (params != null ) {
8285 String requestId = params .get ("requestId" ) != null ? params .get ("requestId" ).toString () : "unknown" ;
8386 String reason = params .get ("reason" ) != null ? params .get ("reason" ).toString () : "No reason provided" ;
84-
87+
8588 LOGGER .info ("Received cancellation notification for request ID: " + requestId + ", reason: " + reason );
86-
89+
8790 // According to MCP spec, cancellation notifications should not send a response
8891 // They are "fire and forget" notifications
8992 res .setResult (null );
@@ -110,6 +113,14 @@ public String handleRpcRequest(Request request, Response response) throws Applic
110113 // Validate authentication
111114 authHandler .validateAuthHeader (request );
112115
116+ // Session management: extract or assign sessionId
117+ String sessionId = (String ) request .headers ().get (Header .value0f (Http .SESSION_ID ));
118+ if (sessionId == null || sessionId .isEmpty ()) {
119+ sessionId = request .getSession ().getId ();
120+ }
121+ response .addHeader (Http .SESSION_ID , sessionId );
122+ currentSessionId .set (sessionId );
123+
113124 // Parse the JSON-RPC request
114125 String requestBody = request .body ();
115126 assert requestBody != null ;
@@ -120,14 +131,8 @@ public String handleRpcRequest(Request request, Response response) throws Applic
120131 }
121132
122133 if (requestBody .contains ("\" method\" :\" initialize\" " )) {
123- // Session management: extract or assign sessionId
124- String sessionId = (String ) request .headers ().get (Header .value0f ("Mcp-Session-Id" ));
125- if (sessionId == null || sessionId .isEmpty ()) {
126- sessionId = request .getSession ().getId ();
127- }
128- response .addHeader ("Mcp-Session-Id" , sessionId );
129- sessionMap .put (sessionId , System .currentTimeMillis ()); // Store session state as needed
130- sessionState = SessionState .INITIALIZING ; // Set initial state
134+ sessionMap .put (sessionId , System .currentTimeMillis ()); // Store session start time
135+ setSessionState (SessionState .INITIALIZING ); // Set initial state
131136 }
132137 // Add batch request support
133138 else if (requestBody .trim ().startsWith ("[" )) {
@@ -136,7 +141,8 @@ else if (requestBody.trim().startsWith("[")) {
136141 if (handler != null ) {
137142 handler .handle (rpcReq , rpcRes , this );
138143 } else {
139- rpcRes .setError (new JsonRpcError (ErrorCodes .METHOD_NOT_FOUND , "Method not found: " + rpcReq .getMethod ()));
144+ rpcRes .setError (new JsonRpcError (ErrorCodes .METHOD_NOT_FOUND ,
145+ "Method not found: " + rpcReq .getMethod ()));
140146 }
141147 });
142148 }
@@ -160,7 +166,10 @@ else if (requestBody.trim().startsWith("[")) {
160166 } catch (Exception e ) {
161167 LOGGER .log (Level .SEVERE , "RPC request failed" , e );
162168 response .setStatus (ResponseStatus .INTERNAL_SERVER_ERROR );
163- return jsonRpcHandler .createErrorResponse ("Internal server error: " + e .getMessage (), ErrorCodes .INTERNAL_ERROR );
169+ return jsonRpcHandler .createErrorResponse ("Internal server error: " + e .getMessage (),
170+ ErrorCodes .INTERNAL_ERROR );
171+ } finally {
172+ currentSessionId .remove ();
164173 }
165174 }
166175
@@ -173,7 +182,7 @@ else if (requestBody.trim().startsWith("[")) {
173182 */
174183 protected void handleInitialize (JsonRpcRequest request , JsonRpcResponse response ) {
175184 // Set protocolVersion as required
176- String protocolVersion = "2024-11-05" ;
185+ String protocolVersion = PROTOCOL_VERSION ;
177186
178187 // Build capabilities object as required by MCP
179188 Builder capabilities = new Builder ();
@@ -195,21 +204,21 @@ protected void handleInitialize(JsonRpcRequest request, JsonRpcResponse response
195204
196205 // Build serverInfo with name, title, and version
197206 Builder serverInfo = new Builder ()
198- .put ("name" , "TinyStructMCP " )
199- .put ("title" , "TinyStruct MCP Server" )
207+ .put ("name" , "tinystruct-mcp " )
208+ .put ("title" , "tinystruct MCP Server" )
200209 .put ("version" , version ());
201210
202211 // Build result object
203212 Builder result = new Builder ()
204213 .put ("protocolVersion" , protocolVersion )
205214 .put ("capabilities" , capabilities )
206215 .put ("serverInfo" , serverInfo )
207- .put ("instructions" , "Welcome to TinyStruct MCP." );
216+ .put ("instructions" , "Welcome to tinystruct MCP." );
208217
209218 response .setId (request .getId ());
210219 response .setResult (result );
211220 // Set session state to READY
212- sessionState = SessionState .READY ;
221+ setSessionState ( SessionState .READY ) ;
213222 }
214223
215224 /**
@@ -242,12 +251,12 @@ protected void handleGetCapabilities(JsonRpcRequest request, JsonRpcResponse res
242251 * @param response The JSON-RPC response to populate
243252 */
244253 protected void handleShutdown (JsonRpcRequest request , JsonRpcResponse response ) {
245- if (sessionState != SessionState .READY ) {
254+ if (getSessionState () != SessionState .READY ) {
246255 response .setError (new JsonRpcError (ErrorCodes .NOT_INITIALIZED , "Not in ready state" ));
247256 return ;
248257 }
249258
250- sessionState = SessionState .DISCONNECTED ;
259+ setSessionState ( SessionState .DISCONNECTED ) ;
251260
252261 response .setId (request .getId ());
253262 response .setResult (new Builder ().put ("status" , "shutdown_complete" ));
@@ -280,7 +289,7 @@ protected void handleGetStatus(JsonRpcRequest request, JsonRpcResponse response)
280289 response .setError (new JsonRpcError (ErrorCodes .INTERNAL_ERROR , "Session start time invalid" ));
281290 return ;
282291 }
283- result .put ("state" , sessionState .toString ());
292+ result .put ("state" , getSessionState () .toString ());
284293 result .put ("sessionId" , sessionId );
285294 result .put ("uptime" , System .currentTimeMillis () - sessionStart );
286295 response .setId (request .getId ());
@@ -314,26 +323,16 @@ protected String[] getFeatures() {
314323 * @param tool The tool to register
315324 */
316325 public void registerTool (MCPTool tool ) {
317- Builder builder = SchemaGenerator .generateSchema (tool .getClass ());
326+ Class <? extends MCPTool > toolClass = tool .getClass ();
327+ Builder builder = SchemaGenerator .generateSchema (toolClass );
318328 tool .setSchema (builder );
319329 tools .put (tool .getName (), tool );
320330 LOGGER .info ("Registered tool: " + tool .getName ());
321- }
322-
323- /**
324- * Registers a tool class and extracts all its methods as individual tools.
325- * This method scans the tool class for methods annotated with @Action and
326- * registers each method as a separate tool method.
327- *
328- * @param toolInstance The tool instance to register
329- */
330- public void registerToolMethods (Object toolInstance ) {
331- Class <?> toolClass = toolInstance .getClass ();
332331
333332 for (Method method : toolClass .getDeclaredMethods ()) {
334333 Action action = method .getAnnotation (Action .class );
335334 if (action != null ) {
336- MCPTool .MCPToolMethod toolMethod = new MCPTool .MCPToolMethod (method , action , toolInstance );
335+ MCPTool .MCPToolMethod toolMethod = new MCPTool .MCPToolMethod (method , action , tool );
337336 toolMethods .put (toolMethod .getName (), toolMethod );
338337 LOGGER .info ("Registered tool method: " + toolMethod .getName ());
339338 }
@@ -427,4 +426,28 @@ protected void registerRpcHandler(String method, RpcMethodHandler handler) {
427426 */
428427 abstract void handleGetPrompt (JsonRpcRequest request , JsonRpcResponse response );
429428
429+ /**
430+ * Gets the current session state based on the current session ID.
431+ *
432+ * @return The current state
433+ */
434+ protected SessionState getSessionState () {
435+ String sessionId = currentSessionId .get ();
436+ if (sessionId != null ) {
437+ return sessionStates .getOrDefault (sessionId , SessionState .DISCONNECTED );
438+ }
439+ return SessionState .DISCONNECTED ;
440+ }
441+
442+ /**
443+ * Sets the session state for the current session.
444+ *
445+ * @param state The new state
446+ */
447+ protected void setSessionState (SessionState state ) {
448+ String sessionId = currentSessionId .get ();
449+ if (sessionId != null ) {
450+ sessionStates .put (sessionId , state );
451+ }
452+ }
430453}
0 commit comments