Преглед изворни кода

fix: RikkaHub MCP compatibility - remove SSE GET, all JSON responses

- Removed SSE GET endpoint (not needed by streamable-http)
- All responses now application/json (fixed text/plain errors)
- OPTIONS preflight returns JSON
- Notification handler returns JSON
- 404 handler returns JSON
- MCP protocol version 2024-11-05 → 2025-03-26
- Updated mcp command: RikkaHub config with mcpServers wrapper
- Removed MCP status indicator from header

Co-Authored-By: Claude <noreply@anthropic.com>
nonvioce пре 3 месеци
родитељ
комит
293c50f8f4

+ 1 - 1
MCP_SERVER.md

@@ -22,7 +22,7 @@ RikkaHub 使用 Streamable HTTP 传输协议,直接配置即可:
 
 ```json
 {
-  "type": "sse",
+  "type": "streamable-http",
   "url": "http://127.0.0.1:8734/mcp",
   "commonOptions": {
     "name": "IJS - Symja 符号计算",

+ 0 - 1
app/src/main/assets/console.html

@@ -21,7 +21,6 @@
                 <span class="brand-text">某人的计算器</span>
             </div>
             <div class="nav-actions">
-                <span id="mcpStatus" class="mcp-status" title="MCP 服务器状态">⚪</span>
                 <button class="nav-btn" id="helpBtn" title="帮助">?</button>
                 <button class="nav-btn" id="pluginsBtn" title="插件">🔌</button>
                 <button class="nav-btn" id="settingsBtn" title="设置">⚙</button>

+ 15 - 26
app/src/main/assets/console.js

@@ -58,11 +58,9 @@ class SymjaConsole {
         // 加载已保存的设置
         this.loadSettings();
 
-        // MCP 状态更新和事件轮询(仅在服务器开启时运行)
+        // MCP 事件轮询(仅在服务器开启时运行)
         this.mcpEventCount = 0;
         this.mcpPollTimer = null;
-        this.mcpStatusTimer = null;
-        this.updateMcpStatus();
         this.startMcpPollingIfNeeded();
         
         // 快捷键工具栏
@@ -1124,7 +1122,20 @@ class SymjaConsole {
             const running = typeof Android !== 'undefined' && Android.isMcpRunning && Android.isMcpRunning();
             const port = '8734';
             const status = running ? '✅ 运行中' : '⭕ 未启动';
-            this.addOutput(`🌐 MCP 服务器状态: ${status}\n   地址: http://127.0.0.1:${port}/mcp\n   健康检查: http://127.0.0.1:${port}/health\n\n   Claude Desktop 配置:\n   {"mcpServers":{"ijs":{"url":"http://127.0.0.1:${port}/mcp"}}}\n\n   adb 端口转发:\n   adb forward tcp:${port} tcp:${port}`, 'help');
+            const rikkaConfig = JSON.stringify({
+                mcpServers: {
+                    ijs: {
+                        type: "streamable-http",
+                        url: `http://127.0.0.1:${port}/mcp`,
+                        commonOptions: {
+                            name: "IJS - Symja 符号计算",
+                            enable: true,
+                            headers: []
+                        }
+                    }
+                }
+            }, null, 2);
+            this.addOutput(`🌐 MCP 服务器 ${status}\n\n📍 地址: http://127.0.0.1:${port}/mcp\n\n📋 RikkaHub 配置:\n${rikkaConfig}\n\n💻 Claude Desktop 配置:\n{"mcpServers":{"ijs":{"url":"http://127.0.0.1:${port}/mcp"}}}\n\n🔧 adb 转发: adb forward tcp:${port} tcp:${port}`, 'help');
         } catch(e) {
             this.addOutput('🌐 MCP 服务器\n   地址: http://127.0.0.1:8734/mcp', 'help');
         }
@@ -1605,16 +1616,10 @@ class SymjaConsole {
         if (!this.mcpPollTimer) {
             this.mcpPollTimer = setInterval(() => this.pollMcpEvents(), 1000);
         }
-        if (!this.mcpStatusTimer) {
-            this.mcpStatusTimer = setInterval(() => this.updateMcpStatus(), 3000);
-        }
     }
 
     stopMcpPolling() {
         if (this.mcpPollTimer) { clearInterval(this.mcpPollTimer); this.mcpPollTimer = null; }
-        if (this.mcpStatusTimer) { clearInterval(this.mcpStatusTimer); this.mcpStatusTimer = null; }
-        const status = document.getElementById('mcpStatus');
-        if (status) { status.className = 'mcp-status'; status.title = 'MCP 已关闭'; }
     }
 
     getSetting(key, def) {
@@ -2035,22 +2040,6 @@ class SymjaConsole {
         }
     }
 
-    updateMcpStatus() {
-        const status = document.getElementById('mcpStatus');
-        if (!status) return;
-        try {
-            if (typeof Android !== 'undefined' && Android.isMcpRunning && Android.isMcpRunning()) {
-                status.className = 'mcp-status on';
-                status.title = 'MCP 服务器运行中';
-            } else {
-                status.className = 'mcp-status';
-                status.title = 'MCP 服务器未启动';
-            }
-        } catch (e) {
-            status.className = 'mcp-status';
-        }
-    }
-
     pollMcpEvents() {
         try {
             if (typeof Android === 'undefined' || !Android.getMcpEvents) return;

+ 13 - 19
app/src/main/java/com/nonvioce/ijs/McpServer.java

@@ -25,7 +25,7 @@ import java.util.Map;
 public class McpServer extends NanoHTTPD {
 
     private static final String TAG = "McpServer";
-    private static final String PROTOCOL_VERSION = "2024-11-05";
+    private static final String PROTOCOL_VERSION = "2025-03-26";
     private static final String SERVER_NAME = "ijs-mcp";
     private static final String SERVER_VERSION = "1.0";
 
@@ -51,7 +51,7 @@ public class McpServer extends NanoHTTPD {
 
         // CORS preflight
         if ("OPTIONS".equals(method)) {
-            Response r = newFixedLengthResponse(Response.Status.OK, "text/plain", "");
+            Response r = newFixedLengthResponse(Response.Status.OK, "application/json", "{}");
             addCommonHeaders(r);
             return r;
         }
@@ -64,14 +64,6 @@ public class McpServer extends NanoHTTPD {
             return r;
         }
 
-        // MCP SSE endpoint (for server→client notifications)
-        if ("GET".equals(method) && "/mcp".equals(uri)) {
-            String accept = session.getHeaders().getOrDefault("accept", "").toLowerCase();
-            if (accept.contains("text/event-stream")) {
-                return handleSse();
-            }
-        }
-
         // DELETE /mcp terminates session
         if ("DELETE".equals(method) && ("/mcp".equals(uri))) {
             sessionId = java.util.UUID.randomUUID().toString();
@@ -80,6 +72,14 @@ public class McpServer extends NanoHTTPD {
             return r;
         }
 
+        // GET /mcp — Streamable HTTP server check
+        if ("GET".equals(method) && ("/mcp".equals(uri) || "/".equals(uri))) {
+            Response r = newFixedLengthResponse(Response.Status.OK, "application/json",
+                    "{\"status\":\"ok\"}");
+            addCommonHeaders(r);
+            return r;
+        }
+
         // Health check
         if ("GET".equals(method) && "/health".equals(uri)) {
             Response r = newFixedLengthResponse(Response.Status.OK, "application/json",
@@ -88,7 +88,8 @@ public class McpServer extends NanoHTTPD {
             return r;
         }
 
-        return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "Not Found");
+        return newFixedLengthResponse(Response.Status.NOT_FOUND, "application/json",
+                "{\"error\":\"not found\"}");
     }
 
     private void addCommonHeaders(Response r) {
@@ -98,13 +99,6 @@ public class McpServer extends NanoHTTPD {
         r.addHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
     }
 
-    /** SSE 端点:发送 endpoint 事件并保持连接 */
-    private Response handleSse() {
-        return newFixedLengthResponse(Response.Status.OK, "text/event-stream",
-            "event: endpoint\r\n" +
-            "data: /mcp?sessionId=" + sessionId + "\r\n\r\n");
-    }
-
     // ── JSON-RPC dispatch ─────────────────────────────────────────
 
     private Response handleJsonRpc(IHTTPSession session) {
@@ -143,7 +137,7 @@ public class McpServer extends NanoHTTPD {
             // Notification (no id) — no response expected
             if (id == null) {
                 handleNotification(method, request.optJSONObject("params"));
-                return newFixedLengthResponse(Response.Status.NO_CONTENT, "text/plain", "");
+                return newFixedLengthResponse(Response.Status.NO_CONTENT, "application/json", "");
             }
 
             // Regular request