DEV Community

Cover image for How I built a zero-install Remote MCP Server for Cursor & Claude (and why local npm packages suck)
Benjian Dai
Benjian Dai

Posted on

How I built a zero-install Remote MCP Server for Cursor & Claude (and why local npm packages suck)

Anthropic’s Model Context Protocol (MCP) is arguably the most exciting architectural standard to hit developer tooling in the last twelve months. It gives AI coding assistants—like Cursor, Claude Desktop, Windsurf, and Cline—a standardized way to reach into databases, APIs, and tools.

Yet, if you’ve actually tried installing third-party MCP servers, you’ve probably hit the dirty secret of the current ecosystem:

The developer experience of local stdio packages is surprisingly painful.

To install a typical community MCP server today, you must:

  1. Have Node.js or Python pre-configured on your host machine.
  2. Manually edit an obscure JSON config file (claude_desktop_config.json or Cursor settings).
  3. Spawn processes via npx -y @some/package that hang on slow connections or Windows shell quirks.
  4. Deal with zombie processes running in the background when your IDE restarts.
  5. Manually remember to update package versions when upstream APIs change.

There is a much better way: Remote MCP over Server-Sent Events (SSE).

Instead of running an arbitrary executable on your local laptop, you give your IDE a single URL:

{
  "mcpServers": {
    "saidithere": {
      "url": "https://saidithere.com/api/mcp"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Zero installs. No Node runtime required. Instant updates. It just works across macOS, Linux, and Windows.

Here is why I built a Remote MCP server for Said It Here, how the SSE protocol actually works under the hood, and how you can implement one in TypeScript with Next.js App Router.


The Motivation: Solving AI Coding Hallucinations with Human Consensus

When your AI assistant hits an edge case—an obscure Next.js cache bug, an AWS IAM permission quirk, or a broken dependency update—LLMs love to hallucinate. They confidently invent parameters that don't exist, recommend deprecated packages, or send you down a 3-hour rabbit hole.

Meanwhile, hundreds of real engineers on Reddit (r/experienceddevs, r/webdev, r/sysadmin) and Hacker News have usually banged their heads against the exact same wall and posted battle-tested workarounds.

I created Said It Here to aggregate these recurring engineering difficulties across developer forums and index the workarounds that humans actually tried.

Instead of forcing developers to context-switch to a browser and sift through Google search results (which are now polluted with SEO spam), I wanted Cursor and Claude to query human consensus directly from inside the IDE.

To do that, we needed an MCP server that any developer could connect to in 10 seconds.


How Remote MCP Works Under the Hood

Under the official MCP specification, an SSE transport works through a bi-directional handshake:

[Cursor / Claude IDE]                      [Next.js Remote MCP Server]
         |                                               |
         | ---- 1. GET /api/mcp (text/event-stream) ---> | (Establishes persistent SSE)
         | <--- 2. event: endpoint, data: /api/mcp?... - | (Returns unique sessionId)
         |                                               |
         | ---- 3. POST /api/mcp?sessionId=... --------> | (Sends JSON-RPC "initialize")
         | <--- 4. event: message, data: {result: ...} - | (Streamed back over SSE)
         |                                               |
         | ---- 5. POST /api/mcp?sessionId=... --------> | (Sends JSON-RPC "tools/call")
         | <--- 6. event: message, data: {result: ...} - | (Streamed back over SSE)
Enter fullscreen mode Exit fullscreen mode
  1. Persistent SSE Stream: The client opens an HTTP GET request with Accept: text/event-stream. The server keeps this connection open indefinitely.
  2. Endpoint Discovery: As soon as the connection opens, the server generates a cryptographically random sessionId and sends an initial endpoint event:
   event: endpoint
   data: /api/mcp?sessionId=3a8f1b...
Enter fullscreen mode Exit fullscreen mode
  1. JSON-RPC Over HTTP POST: The client uses standard HTTP POST requests to send JSON-RPC 2.0 payloads (initialize, tools/list, tools/call, ping) to the endpoint provided.
  2. Asynchronous Streaming: The server processes the request and streams the response back to the client over the already-open SSE pipe.

Implementing Remote MCP in Next.js App Router

Here is a simplified look at how we implemented this in Next.js (web/app/api/mcp/route.ts):

1. Managing Active SSE Sessions

We use a simple in-memory session registry with automated TTL cleanup:

// Active session tracking
interface McpSession {
  id: string;
  writer: WritableStreamDefaultWriter<Uint8Array>;
  encoder: TextEncoder;
  createdAt: number;
}

const sessions = new Map<string, McpSession>();

function broadcast(session: McpSession, event: string, data: unknown) {
  const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
  return session.writer.write(session.encoder.encode(payload));
}
Enter fullscreen mode Exit fullscreen mode

2. The GET Handler (Establishing the Stream)

When Cursor connects, we create a TransformStream, emit the endpoint URI, and launch a periodic keepalive loop to prevent Cloudflare or reverse proxies from killing the connection:

export async function GET(request: Request) {
  const sessionId = crypto.randomUUID();
  const encoder = new TextEncoder();
  const { readable, writable } = new TransformStream();
  const writer = writable.getWriter();

  const session: McpSession = { id: sessionId, writer, encoder, createdAt: Date.now() };
  sessions.set(sessionId, session);

  // Send the required endpoint URI event
  const endpointUrl = `/api/mcp?sessionId=${sessionId}`;
  await broadcast(session, "endpoint", endpointUrl);

  // 15-second keepalive comment to defeat proxy idle timeouts
  const keepAliveTimer = setInterval(() => {
    writer.write(encoder.encode(": keepalive\n\n")).catch(() => {
      clearInterval(keepAliveTimer);
      sessions.delete(sessionId);
    });
  }, 15000);

  return new Response(readable, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache, no-transform",
      "Connection": "keep-alive",
      "Access-Control-Allow-Origin": "*",
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

3. The POST Handler (Dispatching Tool Calls)

When Cursor calls a tool—for instance, search_problems—it sends a standard JSON-RPC 2.0 message:

export async function POST(request: Request) {
  const { searchParams } = new URL(request.url);
  const sessionId = searchParams.get("sessionId");
  const session = sessionId ? sessions.get(sessionId) : null;

  if (!session) {
    return NextResponse.json({ error: "Session not found" }, { status: 404 });
  }

  const rpc = await request.json();

  if (rpc.method === "tools/list") {
    await broadcast(session, "message", {
      jsonrpc: "2.0",
      id: rpc.id,
      result: {
        tools: [
          {
            name: "search_problems",
            description: "Search recurring software dilemmas and human-verified workarounds",
            inputSchema: {
              type: "object",
              properties: { q: { type: "string" } },
              required: ["q"],
            },
          },
          {
            name: "get_problem",
            description: "Get full community consensus and workarounds by slug or URL",
            inputSchema: {
              type: "object",
              properties: { slug: { type: "string" } },
              required: ["slug"],
            },
          },
        ],
      },
    });
  }

  if (rpc.method === "tools/call") {
    const { name, arguments: args } = rpc.params;
    // Execute search logic against read-pool...
    const resultText = await executeTool(name, args);

    await broadcast(session, "message", {
      jsonrpc: "2.0",
      id: rpc.id,
      result: {
        content: [{ type: "text", text: resultText }],
      },
    });
  }

  return new Response(null, { status: 202 });
}
Enter fullscreen mode Exit fullscreen mode

Real-World Proof: Live Third-Party Integration

The beauty of exposing a standard Remote MCP & REST API is how quickly other developers can build on it.

Earlier this week, a fellow developer—omniuni (creator of tinai.online, with over 310k karma on Reddit)—integrated our consensus API into production. Their AI assistant platform now features a dedicated "Consensus" tool that lets users across Claude 3.5, Gemini 1.5, and DeepSeek cross-reference code solutions against real Reddit and Hacker News discussions, complete with direct backlink citations.

When you remove the friction of downloading node packages and compiling binaries, integrations go from "maybe next weekend" to "deployed before lunch."


Try It in Cursor in 30 Seconds

You don’t need an API key, you don’t need an account, and you don’t need to npm install anything.

  1. Open Cursor Settings (Cmd + , or Ctrl + ,).
  2. Navigate to Features -> MCP Servers -> Add New MCP Server.
  3. Fill in:
    • Name: saidithere
    • Type: sse
    • Server URL: https://saidithere.com/api/mcp
  4. Or paste directly into your .cursor/mcp.json:
   {
     "mcpServers": {
       "saidithere": {
         "url": "https://saidithere.com/api/mcp"
       }
     }
   }
Enter fullscreen mode Exit fullscreen mode

Now, open your Cursor Composer or Chat and ask:

"What are the most common community-verified workarounds for handling unsustainable developer workloads?"

Cursor will reach out to the remote MCP server, pull human consensus from real forum discussions, and cite the actual sources.


Key Takeaways

  1. Remote MCP is the future of assistant tooling: Local stdio servers are great for reading local files, but for data services, search, and knowledge bases, SSE endpoints eliminate 95% of user friction.
  2. Keepalives are non-negotiable: If your remote MCP server runs behind Cloudflare, AWS ALB, or Nginx, you must stream periodic : keepalive\n\n comments, or your connection will silently drop every 60 seconds.
  3. Open APIs + MCP creates viral loops: By pairing our public REST API and Remote MCP with our recent release of Reader-Contributed Workarounds, developers can both consume consensus and contribute their own solutions directly.

What MCP servers have you connected to your IDE? Are you hosting them locally or remotely? Drop your thoughts below!

Top comments (0)