dev/notes

Spot a mistake? Highlight any text in a post and click Report — it goes straight to the author.

advanced · AI · August 17, 2026 · 2 min read

How to Build and Secure an MCP Server for AI Agents

Build and secure an MCP server so AI agents can use your tools: HTTP transport, authentication, and rate limiting, step by step.

What MCP gives you

The Model Context Protocol (MCP) turns your app’s functions into tools an agent can call — listing data, creating records, running actions — over a standardized interface. Build it once and Claude, local models, and other MCP clients can all use it.

Step 1 — Install the SDK

npm install @modelcontextprotocol/server zod

Step 2 — Define a tool

src/server.ts:

import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";

const server = new McpServer({ name: "my-server", version: "1.0.0" });

server.registerTool(
  "get_weather",
  { description: "Get the current weather for a city.", inputSchema: { city: z.string() } },
  async ({ city }) => ({
    content: [{ type: "text", text: `Sunny, 22°C in ${city}.` }],
  })
);

The Zod schema becomes the agent-visible contract — write good descriptions; the model reads them to decide when to call the tool.

Step 3 — Expose it over HTTP

Wrap the server in a Streamable HTTP handler and require auth:

import { createMcpHandler, requireBearerAuth } from "@modelcontextprotocol/server";

const gate = requireBearerAuth({
  verifier: {
    async verifyAccessToken(token) {
      if (token !== process.env.ADMIN_PASSWORD) {
        throw new Error("Invalid token");
      }
      return { token, clientId: "devnotes", scopes: [] };
    },
  },
});

const handler = createMcpHandler(() => server);

export default {
  async fetch(request: Request) {
    const auth = await gate(request);
    if (auth instanceof Response) return auth;
    return handler.fetch(request, { authInfo: auth });
  },
};

Step 4 — Add rate limiting

Cap failed auth attempts and overall requests per IP:

const buckets = new Map<string, number>();

function rateLimit(key: string, max: number, windowMs: number): boolean {
  const now = Date.now();
  const count = buckets.get(key) || 0;
  if (count >= max) return false;
  buckets.set(key, count + 1);
  setTimeout(() => buckets.delete(key), windowMs);
  return true;
}

In production, prefer Cloudflare’s rate-limit rules over in-memory maps, which reset across edge isolates.

Step 5 — Connect Claude Code

{
  "mcpServers": {
    "my-server": {
      "type": "http",
      "url": "https://example.com/mcp",
      "headers": { "Authorization": "Bearer <ADMIN_PASSWORD>" }
    }
  }
}

Restart Claude Code and run /mcp to confirm the tools appear.

Security checklist

  • Authenticate every request — never expose tools anonymously.
  • Compare secrets in constant time — avoid timing side channels.
  • Validate all inputs — the Zod schema is the minimum; enforce invariants server-side too.
  • Allowlist redirect URIs if you add OAuth, or a malicious client can phish a code.
  • Return drafts, don’t auto-commit — let the human/agent decide what to save.

Summary

You have a secured, HTTP-served MCP server whose tools agents can discover and call. From here, add OAuth for hosted connectors or a stdio transport for local models.

Related posts

Comments

One comment per thread every 30 minutes · edits are unlimited.