Skip to content

Repository files navigation

mcp-core-engine

A production-style MCP (Model Context Protocol) server packaged as a full-stack web app. It pairs a portfolio/landing page with a live, bearer-authenticated MCP endpoint that exposes six typed utility tools.

Contributor: Muhammad Zeeshan.


How to use

This project is two things at once: a web page you open in a browser, and an MCP server that AI clients (Cursor, Claude Desktop, etc.) connect to. Here is the end-to-end happy path.

1. Install

bun install            # or: npm install --legacy-peer-deps

2. Run it locally

The dev server reads the bearer secret from the shell environment, so export it on the same line that starts the server (see the local-dev note for why):

# macOS / Linux
MCP_SECRET=local-dev-secret-3f9a2c7b6e1d4805 bun run dev
# Windows PowerShell
$env:MCP_SECRET="local-dev-secret-3f9a2c7b6e1d4805"; bun run dev

The terminal prints the URL (currently http://localhost:8080/).

3. Open the landing page

Visit http://localhost:8080/ — it documents the live endpoint and the full tool registry.

4. Call a tool (quick check with curl)

curl -X POST http://localhost:8080/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer local-dev-secret-3f9a2c7b6e1d4805" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

You should get back the six tools and their JSON schemas.

5. Connect an AI client (Cursor / Claude Desktop)

Add this to your MCP client config — in Cursor that's .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "mcp-core-engine": {
      "url": "http://localhost:8080/api/mcp",
      "headers": {
        "Authorization": "Bearer local-dev-secret-3f9a2c7b6e1d4805"
      }
    }
  }
}

Reload the client; mcp-core-engine should appear with 6 tools you can call from chat. For a deployed server, swap the URL and use your real MCP_SECRET.

Heads-up: .cursor/mcp.json contains the bearer token — don't commit it.

Local-dev note. bun run dev serves SSR from the Node process, so it reads MCP_SECRET from process.env and does not auto-load .dev.vars (that's a wrangler dev behavior). If you start the server without the env var, every MCP request returns 401. In production, wrangler secret put

  • nodejs_compat populates process.env for you.

For everything beyond this happy path — deployment, security model, adding tools, testing — see the sections below.


Routes

Route File Purpose
/ src/routes/index.tsx Portfolio landing page documenting the MCP capabilities
/api/mcp src/routes/api/mcp.ts Authenticated MCP endpoint. POST only (GET/DELETE → 405)

MCP tools

Each tool lives in its own file under src/lib/mcp/tools/ — a Zod schema plus a handler, registered in src/routes/api/mcp.ts.

Tool File Description
greet greeting.ts Greet a user by name.
echo echo.ts Echo a message back (connectivity test).
current_time time.ts Current time in ISO 8601 + a chosen IANA timezone.
calculate math.ts Safe arithmetic evaluator (+ - * / %, parentheses, decimals).
fetch_url fetch-url.ts Fetch a public http(s) URL with an SSRF guard, 10s timeout, byte cap.
generate_uuid uuid.ts Generate 1–50 RFC 4122 v4 UUIDs.

Prerequisites

  • Node.js 20+ (developed against v22)
  • Bun is the project's package manager (bun.lock, bunfig.toml). npm also works but requires --legacy-peer-deps because mcp-tanstack-start declares a zod@^3 peer while this project uses zod@^4.

Install

# Preferred
bun install

# Or with npm (note the flag — peer dep mismatch with zod)
npm install --legacy-peer-deps

Configure

Environment variables are validated with Zod in src/lib/server/env.ts (memoized, fail-closed). The MCP endpoint is gated by a bearer token read from MCP_SECRET. If MCP_SECRET is unset, all MCP requests are rejected.

Variable Required Default Purpose
MCP_SECRET yes* Bearer token for /api/mcp (min 16 chars)
RATE_LIMIT_MAX no 60 Requests allowed per window, per client IP
RATE_LIMIT_WINDOW_MS no 60000 Rate-limit window size in milliseconds
LOG_LEVEL no info debug | info | warn | error
NODE_ENV no development Standard environment marker

* Without it the server runs but rejects every MCP request (fail-closed).

For local development with Wrangler, create a .dev.vars file (gitignored) in the project root:

# .dev.vars
MCP_SECRET=your-long-random-secret

Develop

# Export the secret on the same line (see the local-dev note in "How to use")
MCP_SECRET=local-dev-secret-3f9a2c7b6e1d4805 bun run dev   # Vite dev server

Visit the landing page at the dev URL printed in the terminal (currently http://localhost:8080/), and POST to /api/mcp with your bearer token.

Scripts

Script Command Purpose
bun run dev vite dev Local dev server
bun run build vite build Production build
bun run preview vite preview Preview the production build
bun run lint eslint . Lint
bun run typecheck tsc --noEmit Type-check the whole project
bun run format prettier --write . Format
bun run test vitest run Run the unit suite once
bun run test:watch vitest Run unit tests in watch mode
bun run test:e2e playwright test Run the Playwright e2e suite

Security & hardening

This server is hardened by construction (see architecture/ for the ADRs and tradeoffs):

  • SSRF protection (src/lib/server/ssrf.ts) — fetch_url validates the protocol, a hostname blocklist, and every DNS-resolved A/AAAA address (via DNS-over-HTTPS, since Workers has no node:dns), blocking DNS-rebinding to private/loopback/link-local/CGNAT ranges in both IPv4 and IPv6.
  • Bearer auth, fail-closed — missing/invalid MCP_SECRET401.
  • Rate limiting (src/lib/server/rate-limit.ts) — per-client-IP sliding window with RateLimit-* + Retry-After headers; 429 when exceeded.
  • Security headers (src/lib/server/security-headers.ts) — content-aware CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP, applied at a single choke point in server.ts.
  • Structured logging (src/lib/logger.ts) — one JSON object per line; ESLint forbids raw console elsewhere.

Calling the MCP server

All requests require an Authorization: Bearer <MCP_SECRET> header and must accept both JSON and SSE.

List tools

curl -X POST http://localhost:8080/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $MCP_SECRET" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Call a tool

curl -X POST http://localhost:8080/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $MCP_SECRET" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": { "name": "generate_uuid", "arguments": { "count": 3 } }
  }'

Connect an MCP client (Cursor, Claude Desktop, etc.)

{
  "mcpServers": {
    "mcp-core-engine": {
      "url": "https://your-deployment.example.com/api/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_MCP_SECRET"
      }
    }
  }
}

Deployment (Cloudflare Workers)

The Worker entry is src/server.ts (see wrangler.jsonc), which wraps the TanStack Start SSR handler and normalizes catastrophic SSR errors into a branded 500 page.

  1. Authenticate Wrangler (one-time):

    bunx wrangler login
  2. Set the production secret (do not commit it):

    bunx wrangler secret put MCP_SECRET
  3. Build and deploy:

    bun run build
    bunx wrangler deploy

wrangler.jsonc highlights:

  • name: tanstack-start-app
  • main: src/server.ts
  • compatibility_flags: ["nodejs_compat"]

Adding a new tool

  1. Create src/lib/mcp/tools/my-tool.ts:

    import { defineTool } from "mcp-tanstack-start";
    import { z } from "zod";
    
    export const myTool = defineTool({
      name: "my_tool",
      description: "What it does.",
      parameters: z.object({
        input: z.string().min(1).describe("..."),
      }),
      execute: async ({ input }) => `result for ${input}`,
    });
  2. Register it in src/routes/api/mcp.ts by importing it and adding it to the tools: [...] array.

  3. Add a test in src/lib/mcp/tools/tools.test.ts.


Testing

Unit (Vitest)src/**/*.test.ts, run in a plain Node environment (vitest.config.ts). They cover each tool's execute handler and Zod schema, the SSRF IP classifier + assertPublicUrl (with an injected resolver), the rate limiter (with injected now()), env validation, and the security headers.

bun run test        # 82 tests

E2E (Playwright)e2e/*.spec.ts, run against the dev server (which boots the Worker in workerd, so headers/auth/rate-limit behave like production). They assert the landing page renders + hydrates under the CSP, the 405/401 responses, the security headers, and that repeated POSTs get rate-limited.

bunx playwright install chromium   # first time only
bun run test:e2e

Point the e2e suite at a deployed URL instead of the dev server:

E2E_BASE_URL=https://your-deployment.example.com bun run test:e2e

CI/CD

.github/workflows/ci.yml runs on every push/PR:

  1. verifylinttypecheck → unit testbuild
  2. e2e — Playwright (Chromium)
  3. deploy — on main, wrangler deploy. Opt in by setting repo variable CLOUDFLARE_DEPLOY=true plus secrets CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID. Without the opt-in, the deploy job is skipped (so CI stays green). MCP_SECRET is provisioned separately via wrangler secret put.

The same gates run in a pinned container for local reproducibility:

docker build -f infra/Dockerfile -t mcp-core-engine .
docker run --rm mcp-core-engine

The Dockerfile is a build/CI artifact only — production runs on Cloudflare Workers via Wrangler (see ADR-0007).


Project structure

architecture/                 # ADRs (decisions + tradeoffs)
infra/Dockerfile              # Hermetic build/CI image (not the deploy artifact)
e2e/                          # Playwright specs
.github/workflows/ci.yml      # lint → typecheck → unit → build → e2e → deploy
src/
├── server.ts                 # Worker entry: SSR + error normalize + security headers
├── start.ts                  # TanStack Start instance + error middleware
├── router.tsx                # Router factory (React Query context)
├── routeTree.gen.ts          # Auto-generated route tree (do not edit)
├── routes/
│   ├── __root.tsx            # Root layout / providers / error + 404 pages
│   ├── index.tsx             # Landing page
│   └── api/mcp.ts            # MCP route: rate-limit → auth → handler
├── lib/
│   ├── logger.ts             # Isomorphic structured-JSON logger
│   ├── server/               # Server-only trust boundary
│   │   ├── env.ts            # Zod-validated, memoized env
│   │   ├── rate-limit.ts     # Injectable sliding-window limiter
│   │   ├── security-headers.ts # CSP / HSTS / nosniff / frame-ancestors
│   │   └── ssrf.ts           # IP classifier + DoH resolver + assertPublicUrl
│   ├── mcp/tools/            # One file per MCP tool (+ tools.test.ts)
│   ├── error-capture.ts      # SSR error capture
│   ├── error-page.ts         # Branded HTML error page
│   └── utils.ts              # cn() helper
├── components/ui/            # shadcn/ui component library
└── hooks/                    # React hooks

Contributor

  • Muhammad Zeeshan

License

No license file is present. Add one before public distribution.

About

A production-style MCP (Model Context Protocol) server packaged as a full-stack web app. It pairs a portfolio/landing page with a live, bearer-authenticated MCP endpoint that exposes six typed utility tools.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages