The batteries-included starting point for building production MCP servers in TypeScript.
Clone, run setup, start building. Ships with STDIO + HTTP transports, security middleware, persistent state, sandboxed execution, CI/CD workflows, and every distribution format you need -- JSR package, native binary, DXT extension, or hosted on Deno Deploy.
You get a working MCP server in under 2 minutes. Not a toy -- a real server with rate limiting, CORS, session management, TLS support, and structured logging already wired up.
Deno gives you superpowers that other runtimes don't. Built-in KV database (no Postgres/Redis to set up), native cron scheduling, sandboxed code execution in microVMs, compile-to-binary, and first-class Deploy hosting. This template puts all of them to work.
Ship anywhere, any way. One codebase produces:
- a local STDIO server for Cursor, Claude Desktop, or any MCP client
- a remote HTTP server with Streamable HTTP transport
- a JSR package anyone can
deno runwithout cloning - a standalone native binary (no runtime needed)
- a DXT extension for one-click Claude Desktop install
- a Deno Deploy app for managed cloud hosting
AI-agent friendly codebase. Small files (<200 LOC), feature-grouped structure, explicit types. Your coding agent can navigate and extend it without context overload.
# Install Deno (if needed)
curl -fsSL https://deno.land/install.sh | sh
# Create your project from template
gh repo create my-mcp-server --template phughesmcr/deno-mcp-template
cd my-mcp-server
# One-time setup -- renames placeholders, then self-destructs
deno task setup
# Start the server (STDIO + HTTP on localhost:3001)
deno task start
# Or launch with MCP Inspector for interactive testing
deno task devConnect from any MCP client:
{
"mcpServers": {
"my-mcp-server": {
"command": "deno",
"args": ["run", "-A", "/absolute/path/to/main.ts"]
}
}
}That's it. You have a running MCP server with tools, resources, prompts, and task workflows.
When this package is published to JSR, you can import the full program (main.ts) or narrower entrypoints:
jsr:@scope/name— same asmain.ts(CLI entry).jsr:@scope/name/mcp—createMcpServer,mcpServerDefinition, context/subscription helpers, and related exports. The host must still open Deno KV (and any other shared services) before handling requests, same ascreateAppdoes.jsr:@scope/name/app—createAppfor embedding the STDIO + HTTP host without copyingmain.ts.
See .cursor/rules/project.mdc (Architecture) for transport-scoped McpServer instances and shared context.
| What | How | Where |
|---|---|---|
| Dual transports | STDIO + Streamable HTTP from a single app | src/app/ |
| HTTP middleware | Rate limiting, CORS, optional bearer auth (not applied to /mcp-elicitation/* browser pages), security headers, timeouts, sessions |
src/app/http/hono.ts |
| Persistent state | Deno KV -- zero-config locally, built-in on Deploy | src/kv/ |
| Session resumability | KV-backed event store for stream recovery | src/app/http/kvEventStore.ts |
| Background tasks | MCP experimental tasks: KV TaskStore + TaskMessageQueue, plus listenQueue worker for the delayed-echo demo |
src/mcp/tasks/ |
| Scheduled jobs | Deno.cron for periodic maintenance |
src/app/cron.ts |
| Sandboxed execution | @deno/sandbox microVMs for untrusted code |
src/mcp/tools/sandbox.ts |
| MCP Apps (interactive UI) | Example tool + ui:// HTML bundle via @modelcontextprotocol/ext-apps |
mcp-ui/, src/mcp/apps/, static/mcp-apps/ |
| CI/CD workflows | GitHub Actions for CI, release, deploy, JSR publish | .github/workflows/ |
| Config management | CLI flags + env vars with validation and merging | src/app/cli.ts |
| Permission preflight | Fail-fast startup checks with actionable guidance | src/app/permissions.ts |
Prompts: review-code, language-snippet
Resources: hello://world, greetings://{name}, counter://value (KV-backed, subscribable)
Tools: elicit-input, elicit-form-wizard (two-step form elicitation), url-elicitation-demo (URL-mode elicitation; streamable HTTP with a session only), fetch-website-info (text + optional MCP Apps UI in supporting clients), increment-counter, log-message, notify-list-changed, poem (sampling), execute-code (sandboxed)
Task workflows (experimental): delayed-echo, guided-poem (elicitation + sampling pipeline)
These cover prompts with arguments, static and dynamic resources, subscriptions, sampling, form and URL elicitation, notifications, list-changed events, KV persistence, sandboxed execution, async task patterns, and an MCP Apps UI example (fetch-website-info). Use them as reference, then swap in your domain logic.
Run deno task setup first -- it rewrites package names, scopes, and metadata across the project in one pass.
src/shared/constants/-- server name, description, version, defaultssrc/mcp/tools/-- add your tools (follow existing patterns)src/mcp/apps/-- register MCP App tools/resources (@modelcontextprotocol/ext-apps/server) when you add interactive UIsmcp-ui/-- Vite bundle for MCP App HTML (rundeno task build:mcp-ui; Deno installs npm deps and runs Vite — seemcp-ui/README.md)src/mcp/resources/-- add your resourcessrc/mcp/prompts/-- add your promptssrc/mcp/serverDefinition.ts-- feature lists, capability flags, and derivedSERVER_CAPABILITIES(re-exported fromsrc/shared/constants/mcp.ts)src/mcp/mod.ts-- server construction (registration follows the definition)src/app/http/hono.ts-- adjust CORS, middleware, routes
Delete the example files you don't need from src/mcp/tools/, src/mcp/resources/, src/mcp/prompts/, and (if you drop MCP Apps) src/mcp/apps/ plus mcp-ui/. Update the corresponding mod.ts barrel exports and any registration in src/mcp/mod.ts. Done.
- Authentication: Set
MCP_HTTP_BEARER_TOKEN(or--http-bearer-token) so clients must sendAuthorization: Bearer …orx-api-key: …on/mcp. For CI or production templates,MCP_REQUIRE_HTTP_AUTH=truefails startup if no token is set. Paths under/mcp-elicitation/intentionally skip bearer auth so normal browser tabs can open URL-mode elicitation pages without the MCP token. - Public URL for links: Behind a reverse proxy, set
MCP_PUBLIC_BASE_URL(or--public-base-url) to thehttps://origin users open in a browser so URL elicitation links match your deployment. If unset, the server derives a URL from the bind address (seesrc/shared/publicBaseUrl.ts). - Exposure: Binding to a non-loopback hostname without a token logs a warning: anyone who can reach the port can use MCP. Use a token or terminate TLS and auth at a reverse proxy.
- All interfaces: Listening on
0.0.0.0or::requires--dnsRebindingplus--host/MCP_ALLOWED_HOSTS(validated at startup). - CORS: Wildcard
*origins are not allowed; list explicit origins (e.g.MCP_ALLOWED_ORIGINS). - Rate limits: With
MCP_TRUST_PROXY=true, limits follow proxy client IP headers (only safe behind a real proxy). Requests with no socket IP and no session use a lower cap (RATE_LIMIT_UNKNOWN_CLIENTinsrc/shared/constants/http.ts). fetch-website-info: Only public HTTPS URLs are allowed by default (blocks private IPs, localhost, link-local,.internal, etc.). Redirects are followed manually with the same checks. SetMCP_DOMAIN_TOOL_ALLOW_HTTP=1to allowhttp://for demos. MCP Apps-capable hosts may loadui://deno-mcp-template/fetch-website-info.htmlfor an inline UI; others still get JSON text in the tool result.
See .env.example for copy-paste variables.
| Goal | Command | Output |
|---|---|---|
| Run locally via STDIO | deno task start |
Direct process |
| Serve over HTTP | deno task start |
localhost:3001/mcp |
| Publish as JSR package | deno publish |
jsr:@scope/package |
| Compile native binary | deno task compile:all |
dist/server/ |
| Package as DXT | deno task dxt:all |
dist/server.dxt |
| Deploy to cloud | deno deploy --prod |
Deno Deploy URL |
Platform-specific compile/DXT tasks are also available: compile:win, compile:mac:arm64, compile:mac:x64, compile:linux:x64, compile:linux:arm64 (and matching dxt:* variants).
Compile tasks bundle the static/ directory, so binaries serve static routes without depending on the working directory.
DXT prerequisite: install the DXT CLI once with npm install -g @anthropic-ai/dxt. Update static/dxt-manifest.json before packaging.
JSR prerequisite: you need a JSR.io account. If you don't plan to publish on JSR, remove .github/workflows/publish.yml.
STDIO (Cursor, Claude Desktop, etc.):
{
"mcpServers": {
"my-mcp-server": {
"command": "deno",
"args": ["run", "-A", "/absolute/path/to/main.ts"]
}
}
}HTTP with mcp-remote:
{
"mcpServers": {
"my-mcp-server": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:3001/mcp"]
}
}
}HTTP direct (clients with URL support):
{
"mcpServers": {
"my-mcp-server": {
"url": "http://localhost:3001/mcp",
"headers": { "origin": "http://localhost:3001" }
}
}
}JSR package:
{
"mcpServers": {
"my-mcp-server": {
"command": "deno",
"args": ["run", "-A", "jsr:@your-scope/your-package"]
}
}
}Compiled binary:
{
"mcpServers": {
"my-mcp-server": {
"command": "/absolute/path/to/binary"
}
}
}Claude Code:
claude mcp add my-mcp-server /absolute/path/to/binary
claude mcp add --transport http my-mcp-server http://localhost:3001/mcp# Create app (one-time)
deno deploy create \
--org <YOUR_ORG> \
--app <YOUR_APP> \
--source local \
--runtime-mode dynamic \
--entrypoint main.ts \
--build-timeout 5 \
--build-memory-limit 1024 \
--region us
# Deploy
deno deploy --prodSet DENO_DEPLOY_TOKEN, DENO_DEPLOY_ORG, and DENO_DEPLOY_APP in GitHub Actions secrets for automatic deploys. Remove .github/workflows/deploy.yml if not using Deploy.
- Run
deno task setupand verify all placeholder names are replaced - Remove or replace demo tools/resources/prompts you won't ship
- Update
static/.well-known/openapi.yamlandstatic/dxt-manifest.json - Configure
MCP_ALLOWED_ORIGINSandMCP_ALLOWED_HOSTSfor your environments - Replace
src/app/http/kvEventStore.tswith a production-grade event store (the included one is a demo) - Switch from
deno run -Ato explicit permissions for production - Review
--allow-netscope for any outbound tool calls - Set environment variables in CI and hosting provider
- Run
deno task ci(format, lint, type-check, test)
| Variable | Flag | Default | Description |
|---|---|---|---|
MCP_NO_HTTP |
--no-http |
false |
Disable HTTP server |
MCP_NO_STDIO |
--no-stdio |
false |
Disable STDIO transport |
MCP_HOSTNAME |
-n |
localhost |
HTTP listen hostname |
MCP_PORT |
-p |
3001 |
HTTP listen port |
MCP_PUBLIC_BASE_URL |
--public-base-url |
Public http(s):// origin for browser links (URL elicitation); no trailing slash |
|
MCP_TLS_CERT |
--tls-cert |
PEM certificate path (requires --tls-key) |
|
MCP_TLS_KEY |
--tls-key |
PEM private key path (requires --tls-cert) |
|
MCP_HEADERS |
-H |
Response headers (collection) | |
MCP_JSON_RESPONSE |
--json-response |
false |
JSON-only responses (disable SSE) |
MCP_DNS_REBINDING |
--dnsRebinding |
false |
Enable transport-level Origin/Host checks (loopback binds already get Host validation) |
MCP_ALLOWED_ORIGINS |
--origin |
Allowed CORS origins (collection) | |
MCP_ALLOWED_HOSTS |
--host |
Allowed hostnames (collection) | |
MCP_KV_PATH |
--kv-path |
Custom Deno KV database path | |
MCP_MAX_TASK_TTL_MS |
--max-task-ttl-ms |
86400000 (24h) |
Max client-requested TTL (ms) for experimental MCP tasks; clamped in KvTaskStore (min 60s, max 1y — see src/shared/validation/config.ts) |
DENO_DEPLOY_TOKEN |
Deploy token (required by execute-code sandbox tool) |
CLI flags override env vars. Collection values (-H, --origin, --host) are merged from both sources.
Collection-style values can be set as comma-separated strings in .env:
MCP_HEADERS=x-api-key:demo,cache-control:no-store
MCP_ALLOWED_ORIGINS=http://localhost:6274,https://app.example.com
MCP_ALLOWED_HOSTS=localhost,127.0.0.1,app.example.comOr repeated CLI flags:
deno task start -- \
-H "x-api-key:demo" \
-H "cache-control:no-store" \
--origin "http://localhost:6274" \
--origin "https://app.example.com" \
--host "localhost" \
--host "app.example.com"Use --origin for full origins (e.g. https://example.com) and --host for hostnames or IPs (e.g. example.com, 127.0.0.1).
| Command | What it does |
|---|---|
deno task start |
Start server |
deno task dev |
Start with MCP Inspector + watch mode |
deno task build:mcp-ui |
Build MCP App HTML into static/mcp-apps/ (Deno + mcp-ui/deno.lock; no Node.js) |
deno task ci |
Runs build:mcp-ui, then format, lint, type-check, and test |
deno task test:integration |
Run integration tests |
deno task test:coverage |
Tests with coverage report |
deno task bench |
Run benchmarks |
deno task ci runs build:mcp-ui first, which uses Deno-only install + Vite under mcp-ui/ (see mcp-ui/README.md). CI only needs Deno for that step.
The app validates required permissions at startup and fails fast with actionable guidance. For production, prefer explicit permissions over -A:
# HTTP + STDIO (local defaults)
deno run --env-file=.env \
--allow-env --allow-read --allow-write --allow-sys \
--allow-net=localhost:3001 \
main.ts
# STDIO only (no HTTP listener)
deno run --env-file=.env \
--allow-env --allow-read --allow-write --allow-sys \
main.ts --no-httpIf you keep networked tools (such as fetch-website-info), include their destinations in --allow-net.
This template tracks deno.lock for deterministic dependency resolution.
- Refresh lock data:
deno install --entrypoint main.ts --frozen=false - Verify locked resolution (CI):
deno install --frozen --entrypoint main.ts - Commit
deno.lockwith dependency changes.
main.ts # Entry point
src/
app/ # Runtime shell: transports, HTTP, KV, cron, signals
mcp/ # MCP server: tools, resources, prompts, tasks, apps
shared/ # Constants, types, validation, utilities
mcp-ui/ # Vite + ext-apps front-end for MCP App HTML bundles
static/ # Static files, OpenAPI spec, DXT manifest, mcp-apps/*.html
scripts/ # Setup, build, and packaging helpers
test/ # Integration tests and benchmarks
.github/workflows/ # CI, release, deploy, publish
For the annotated source tree, transport internals, and development caveats, see
.cursor/rules/project.mdc.
Included quality-of-life files:
.cursor/rules/-- Cursor agent rules for this project.cursor/skills/-- Agent skills for implementing tools, resources, and prompts (see below).cursorignore-- tells Cursor to exclude files in addition to.gitignore.vscode/-- recommended extensions, Deno as default formatter.github/-- CI/CD workflows, sponsors config, issue templatesCLAUDE.md-- optional Claude Code project contextCODE_OF_CONDUCT.md,CONTRIBUTING.md, etc. -- community management files for GitHub
The .cursor/skills/ directory contains agent skills that guide Cursor through implementing MCP features in this template. Each skill provides file templates, registration steps, type signatures, and working examples.
| Skill | What it covers |
|---|---|
implementing-mcp-tools |
Standard tools, sampling, form and URL elicitation, resource-backed tools, notifications |
implementing-mcp-resources |
Static resources, KV-backed resources, resource templates, subscriptions |
implementing-mcp-prompts |
Prompts with static arguments or dynamic completions |
These skills are picked up automatically by Cursor when relevant. Ask the agent to "add a new tool" or "create a resource" and it will follow the project's patterns.
- MCP Specification
- MCP Apps /
@modelcontextprotocol/ext-apps(interactive tool UIs) - MCP Concepts
- Deno Docs
- Deno Deploy
- Deno Sandbox
- Hono
If you use this template, please consider contributing fixes and features, starring the repo, and sponsoring.
This is not an official Deno project.
MIT -- this is a template, not a library. You're expected to modify it before shipping to production.
