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.
Comments
One comment per thread every 30 minutes · edits are unlimited.
Signed in as devnotes-admin — this will post under your admin identity.