Skip to content

MCP HTTP JSON-RPC compatibility issue with strict clients (Rider) #11

Description

@mining-ua

Summary

Rider MCP client fails to work with VSC-MCPServer due to JSON-RPC HTTP response compatibility issues.

Observed behavior

  • Server works with more tolerant clients (e.g., VSCode/Antigravity scenarios).
  • Rider fails to parse certain responses and reports request/response validation errors.

Root cause (reproduced)

  1. For JSON-RPC notifications (requests without id), server should not return a JSON-RPC response body.
  2. Some clients probe GET /mcp before POST; returning non-200 can break compatibility.
  3. Error responses must preserve request id when present.

Expected behavior

  • Requests with id → JSON-RPC response includes same id (including errors).
  • Notifications (no id) → no JSON-RPC response body (e.g. 202 Accepted empty body).
  • GET /mcp should be reachable for probing clients.

Proposed fix

  • Preserve id in JSON-RPC error responses.
  • Return 202 with empty body for notifications.
  • Add GET /mcp endpoint returning 200 with service metadata.

Environment

  • Extension: VSC-MCPServer 0.1.0
  • Client: JetBrains Rider MCP integration

Fixed propose

    private setupRoutes(): void {
        const isNotification = (body: unknown): boolean => {
            return !!body && typeof body === 'object' && !(Array.isArray(body)) && !('id' in body);
        };

        const sendNotificationAck = (res: Response): void => {
            res.status(202).end();
        };

        // Health check endpoint
        this.app.get('/health', (req: Request, res: Response) => {
            res.json({ status: 'ok', port: this.actualPort });
        });

        // Keep GET /mcp reachable for clients probing transport capabilities
        this.app.get('/mcp', (req: Request, res: Response) => {
            res.json({ status: 'ok', transport: 'jsonrpc-http', endpoint: '/mcp' });
        });

        // MCP endpoint - simplified JSON-RPC over HTTP
        this.app.post('/mcp', async (req: Request, res: Response) => {
            const id = req.body?.id !== undefined ? req.body.id : null;
            try {
                const { method, params } = req.body;

                if (method === 'initialize') {
                    if (isNotification(req.body)) {
                        sendNotificationAck(res);
                        return;
                    }

                    res.json({
                        jsonrpc: '2.0',
                        id,
                        result: {
                            protocolVersion: '2024-11-05',
                            capabilities: {
                                tools: {},
                            },
                            serverInfo: {
                                name: 'vscode-mcp',
                                version: '0.1.0',
                            },
                        },
                    });
                    return;
                }

                if (method === 'tools/list') {
                    const tools = getAllTools();

                    if (isNotification(req.body)) {
                        sendNotificationAck(res);
                        return;
                    }

                    res.json({
                        jsonrpc: '2.0',
                        id,
                        result: { tools },
                    });
                    return;
                }

                if (method === 'tools/call') {
                    const { name, arguments: args } = params || {};
                    const result = await callTool(name, args || {});

                    if (isNotification(req.body)) {
                        sendNotificationAck(res);
                        return;
                    }

                    res.json({
                        jsonrpc: '2.0',
                        id,
                        result: {
                            content: [
                                {
                                    type: 'text',
                                    text: JSON.stringify(result, null, 2),
                                },
                            ],
                        },
                    });
                    return;
                }

                if (isNotification(req.body)) {
                    sendNotificationAck(res);
                    return;
                }

                res.json({
                    jsonrpc: '2.0',
                    id,
                    error: {
                        code: -32601,
                        message: `Method not found: ${method}`,
                    },
                });
            } catch (error) {
                if (isNotification(req.body)) {
                    sendNotificationAck(res);
                    return;
                }

                const errorMessage = error instanceof Error ? error.message : String(error);
                res.json({
                    jsonrpc: '2.0',
                    id,
                    error: {
                        code: -32603,
                        message: errorMessage,
                    },
                });
            }
        });
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions