DEV Community

Cover image for How to Build an MCP Server in Next.js
Syed Zadi
Syed Zadi

Posted on Originally published at agenticgiants.com

How to Build an MCP Server in Next.js

To build an MCP server in Next.js, you expose a single API route that speaks the Model Context Protocol over Streamable HTTP. It accepts the client's JSON-RPC requests, dispatches each one to a registered tool, and streams results back.

That is the whole shape. The interesting part is everything around it.

What you are actually building

An MCP server is a governed adapter. It gives an AI client scoped, audited access to your tools and data instead of pasting raw API keys into a prompt and hoping.

The route handler is the surface the AI client connects to. Behind it sit the tool implementations that actually touch your systems.

If that framing is new, start with what an MCP server is and come back.

Pick the transport: HTTP, not stdio

MCP defines two transports.

stdio is for a local subprocess the client spawns the server as a child process and talks over standard input and output. Useful for desktop tooling, wrong for a web app.

Streamable HTTP is for a remote server reached over the network. That is what a Next.js route is.

Choose HTTP. It lets many clients connect, and it sits behind the same authentication, rate limiting, and observability you already run in front of your app. You do not have to invent a second operational stack for your AI surface.

A minimal route handler

The shape is a POST handler that:

  1. Authenticates the caller
  2. Parses the JSON-RPC request
  3. Dispatches to the right tool
  4. Returns the result
// app/api/mcp/route.ts
export async function POST(req: Request) {
  const caller = await authenticate(req);   // 1
  if (!caller) return new Response("Unauthorized", { status: 401 });

  const rpc = await req.json();             // 2
  const tool = TOOLS[rpc.params?.name];     // 3
  if (!tool) return jsonRpcError(rpc.id, -32601, "Method not found");

  const args = tool.schema.parse(rpc.params.arguments);
  const result = await tool.run(args, caller);

  return Response.json({                    // 4
    jsonrpc: "2.0",
    id: rpc.id,
    result,
  });
}
Enter fullscreen mode Exit fullscreen mode

Note what happens before any tool runs: the caller is authenticated, and the arguments are parsed against a schema. Neither step is optional.

Exposing tools without handing out the keys

A tool is a named, typed function the model may call.

The temptation is to expose a thin passthrough to an internal API. The discipline is to expose the capability, not the API.

A passthrough tool called callInternalApi(path, method, body) is not a tool. It is a remote shell with extra steps. The model can reach anything the service account can reach, and your audit log records "called the API" rather than "read customer 4821's billing status."

Three rules that hold up in production:

The server holds its own scoped credentials. The model never sees a key. Each tool runs with the narrowest permission that does its job.

No tool is "run arbitrary query." If a tool accepts SQL, Cypher, or a shell string, you have built an injection surface that no amount of prompting will close.

Every input is validated at the boundary. A model or a prompt injection riding inside retrieved data will eventually send something malformed or hostile. Parse with a schema, reject on failure, and log the rejection.

What changes in production

The demo works on your laptop. These are what break first when it does not.

Concern What to add
Authentication Per-caller identity, not a shared secret
Authorization Permission check inside each tool, not just at the route
Rate limiting Per caller and per tool agents retry aggressively
Timeouts Every downstream call needs one; agents will hang otherwise
Audit logging Caller, tool, arguments, result, timestamp immutable
Error shape Return structured JSON-RPC errors, never raw stack traces

The last one matters more than it looks. A stack trace returned to a model becomes context the model reasons over, and leaks your internals into whatever the agent does next.

Where to go next

Once the route is live, the real work is tool design deciding what each tool exposes and, more importantly, what it refuses to.

That is a security question more than an engineering one. MCP server security covers the threat model and a hardening checklist to run before anything touches a system of record.


We build production MCP servers for regulated industries. If you are shipping one, talk to our team.

Top comments (0)