mcp-server-base
Provides a production-ready Model Context Protocol server with dual STDIO and Streamable HTTP transports, enabling file operations, memory, database queries, RAG, web search, GitHub integration, background tasks, and prompt-based workflows.
README
MCP Server Base v3.0 — Scale & Enterprise (2026)
Modern Model Context Protocol server using the latest stack:
- MCP SDK
1.12+—McpServerhigh-level API +StreamableHTTPServerTransport(new) &StdioServerTransport - TypeScript 5.7 ESM +
NodeNextmodule - Zod validation → auto JSON Schema + env validation (
src/config.ts:1) - Express 4 + helmet + CORS allowlist + rate-limit + health/ready + Admin UI
- Dual transport: STDIO (Claude Desktop) and Streamable HTTP (remote, 2025-03 spec, stateless + stateful resumability via RedisEventStore)
- Structured tool/resource/prompt modules + RAG hybrid (BM25+vector), Web (cached), GitHub integrations
- Plugin SDK (
src/plugin/index.ts:1) + integrations (slack/notion/linear), Registry (smithery.yaml, mcpName), Prompt Playground at/admin - v3.0 Enterprise: Multi-tenant (
X-Tenant-Id, namespaced stores), SSO OIDC, Control plane CRUD, cluster mode, Prometheus/Grafana stack - OTEL tracing/metrics (
src/utils/otel.ts:1), Tasks (experimental +create_task), k6 load tests tsxwatch,vitest(178 tests, 88% coverage), graceful shutdown,docker-compose(redis, postgres, qdrant, otel-collector, prometheus, grafana)
🚀 Quick Start
npm install
npm run build
# STDIO (for Claude Desktop, Cursor, opencode, etc.)
npm start
# HTTP (Streamable HTTP - latest)
npm run start:http
# → http://localhost:3000/mcp
# → health http://localhost:3000/health
Dev
npm run dev # stdio watch
npm run dev:http # http watch (Streamable HTTP at http://localhost:3000/mcp)
npm test # unit + e2e (InMemory + HTTP)
npm run test:coverage # coverage 80% thresholds
npm run lint # eslint 9 flat config
npm run format:check # prettier
npm run typecheck # tsc --noEmit
npm run build
CI
.github/workflows/ci.yml runs on push/PR to main with Node 20+22 matrix: lint, format:check, typecheck, test:coverage, build, docker build.
🔌 Transports
| Transport | Use | Command |
|---|---|---|
| STDIO | Local clients (Claude Desktop) | node dist/index.js |
| Streamable HTTP | Remote / Docker / Cloud | node dist/index.js --http |
Streamable HTTP is the new standard replacing SSE (deprecated March 2025).
🧰 Tools (40)
| Tool | Description | Input |
|---|---|---|
echo |
Echo message | message, uppercase? |
calculator |
add/sub/mul/div | operation, a, b |
get_time |
Current time | timezone? |
fetch_url |
Fetch URL | url, maxLength? |
list_files |
List files under ALLOWED_ROOT | path?, recursive? |
read_file |
Read file (1MB limit) | path |
write_file |
Write file + triggers resource changed | path, content |
search_files |
Search text inside files | query, path?, maxResults? |
memory_set |
Set KV in memory | key, value |
memory_get |
Get KV | key |
memory_delete |
Delete KV | key |
memory_list |
List KVs | — |
memory_clear |
Clear all | — |
database_query |
SQL via alasql (users, notes) | sql |
database_tables |
List tables row counts | — |
shell_execute |
Shell (allowlist, disabled by default) | command, timeout? |
collect_user_info |
Elicitation demo (contact/preferences) | infoType? |
generate_with_sampling |
Sampling demo (LLM) | prompt, maxTokens? |
rag_ingest |
Ingest text (chunked, embedded) | text, id?, metadata?, chunk? |
rag_search |
Hybrid search (vector+BM25) | query, topK?, threshold?, mode? |
rag_list |
List docs | — |
rag_clear |
Clear vector store | — |
brave_search |
Brave API (mock if no key) | query, count? |
tavily_search |
Tavily API (mock if no key) | query, maxResults?, includeAnswer? |
web_fetch |
Cached web fetch | url, useCache?, maxLength? |
github_search_repos |
GitHub search repos | query, perPage? |
github_get_repo |
GitHub get repo | repo |
github_get_issue |
GitHub get issue | repo, issueNumber |
create_task |
Create background task | duration?, payload? |
get_task |
Get task status | taskId |
get_task_result |
Get task result | taskId |
slack_list_channels |
Slack channels (plugin) | — |
slack_post_message |
Slack post (plugin) | channel, text |
slack_search |
Slack search (plugin) | query, count? |
notion_search |
Notion search (plugin) | query, page_size? |
notion_get_page |
Notion page (plugin) | page_id |
notion_create_page |
Notion create (plugin) | title, content? |
linear_list_issues |
Linear issues (plugin) | team?, limit? |
linear_create_issue |
Linear create (plugin) | title, description?, team? |
linear_get_issue |
Linear issue (plugin) | id |
📦 Resources (6)
config://server-info— server metadata (JSON, now includesfeatures)greeting://{name}— dynamic greeting templatefile:///{+path}— sandboxed file (ALLOWED_ROOT), list + complete,file:///notes.txtmemory://{key}— memory KV, list + completedb://{table}/{id}— demo DB row (users/notes), list + completedocs://{id}— RAG chunk (ingested viarag_ingest), list + complete
💬 Prompts (4)
code-review— args:language,codeexplain-concept— args:concept,levelsummarize— args:text,length(short/medium/long),style(bullets/paragraph/tldr)research— args:topic,depth(overview/deep),audience(beginner/expert/executive)
⚙️ Client Config
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"mcp-server-base": {
"command": "node",
"args": ["/absolute/path/to/mcp-server/dist/index.js"]
}
}
}
HTTP Client
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const client = new Client({ name: 'my-client', version: '1.0.0' });
await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')));
const tools = await client.listTools();
Inspector
npm run inspect
# or
npx @modelcontextprotocol/inspector node dist/index.js
npx @modelcontextprotocol/inspector http://localhost:3000/mcp
🐳 Docker
# Single container
docker build -t mcp-server-base .
docker run -p 3000:3000 --env TRANSPORT=http mcp-server-base
# Full stack (app + redis + postgres + qdrant) — see docker-compose.yml
docker compose up -d
docker compose logs -f app
# → http://localhost:3000/health, http://localhost:3000/mcp
# → redis :6379, postgres :5432, qdrant :6333
RAG Demo (ingest → search → docs://)
# via MCP tools (Inspector or Client)
# 1. ingest
rag_ingest { "text": "MCP is Model Context Protocol...", "id": "mcp-intro" }
# 2. search
rag_search { "query": "what is MCP?", "topK": 3 }
# 3. read resource
# docs://mcp-intro → returns ingested text
📁 Structure
src/
├── index.ts # entry: stdio + http (helmet/cors/rateLimit/auth/RBAC/OTEL/metrics)
├── server.ts # createMcpServer() factory (v2.1.0, instructions)
├── config.ts # zod env (AUTH, CORS, rateLimit, RAG, cache, integrations, OTEL, admin, tasks)
├── types.ts # Zod schemas
├── middleware/auth.ts # AUTH_MODE none|apiKey|bearer
├── middleware/rateLimit.ts
├── middleware/requestId.ts
├── middleware/rbac.ts # RBAC reader/writer/admin + mcpRbacMiddleware (v2.1)
├── observability/otel-real.ts # OTEL NodeSDK init (v2.1)
├── observability/slo.ts # checkSlo() for /health/ready (v2.1)
├── utils/logger.ts # stderr, JSON/text, redaction, child(requestId)
├── utils/eventStore.ts # InMemoryEventStore
├── utils/redisEventStore.ts # RedisEventStore (scale)
├── utils/cache.ts # MemoryCache (TTL) + defaultCache
├── utils/queue.ts # SimpleQueue
├── utils/metrics.ts # prom-client Registry (v2.1)
├── utils/persistence.ts # save/load backup (v2.1)
├── utils/otel.ts # stub OTEL spans/metrics
├── tools/ # 40 tools: echo, fs, memory, db, shell, rag (hybrid), web, github, elicitation, sampling, tasks
│ ├── filesystem.tool.ts, memory.tool.ts, database.tool.ts, shell.tool.ts
│ ├── rag.tool.ts, web.tool.ts, github.tool.ts, elicitation.tool.ts, sampling.tool.ts, tasks.tool.ts
├── plugin/index.ts # Plugin SDK: definePlugin/registerPlugin (v2.2)
├── integrations/ # slack/notion/linear plugins (v2.2)
├── middleware/tenant.ts # Multi-tenant X-Tenant-Id + scoped stores (v3.0)
├── routes/controlplane.ts# Tenants CRUD + key rotation (v3.0)
├── utils/cluster.ts # Cluster mode horizontal scale (v3.0)
├── resources/ # 6 resources: config, greeting, file, memory, db, docs
├── routes/admin.ts # Admin UI + metrics/spans/stores (v2.0) + /metrics Prometheus (v2.1)
└── prompts/ # 4 prompts: code-review, explain-concept, summarize, research
Add a new tool: create src/tools/my.tool.ts → export registerMyTool(server) → add to src/tools/index.ts.
🔐 Security (Phase 2)
- Helmet headers (
x-dns-prefetch-control,x-frame-options,x-content-type-options, etc.) viahelmet@7(src/index.ts:1) - CORS allowlist (
CORS_ORIGIN=*or comma list) withcorscredentials handling (src/config.ts:60) - Auth
AUTH_MODE=none|apiKey|beareratsrc/middleware/auth.ts:1—401without validX-API-KeyorAuthorization: Bearer(health/ready & OPTIONS excluded) - Rate limiting
express-rate-limit(default 100/15min) on/mcp—429 Too Many Requests(src/middleware/rateLimit.ts:1) - RequestId (
X-Request-IdrandomUUID, echo header, child logger correlation) (src/middleware/requestId.ts:1) - Zod env validation (
src/config.ts:1) —parseEnv()validatesPORT,AUTH_MODE,API_KEYcross-field, fails fast on invalid env - Structured logger JSON/text,
[REDACTED]forauthorization,apiKey,token(src/utils/logger.ts:24) - Resumability
InMemoryEventStore(src/utils/eventStore.ts:1) + stateful session map whenRESUMABILITY_ENABLED=true(replay viaLast-Event-ID,GET /mcpstream,DELETEclose) - Docker hardening non-root
appuser+HEALTHCHECK(Dockerfile:1) - Tests:
tests/unit/auth.test.ts,tests/unit/logger.test.ts,tests/unit/eventStore.test.ts,tests/e2e/security.test.ts(helmet/auth/rateLimit/resumability) —67 tests → 130 total with Phase 5, 90.89% coverage
🔗 Integrations (Phase 4)
- Cache
MemoryCacheTTL (src/utils/cache.ts:1) —defaultCachefor web/github,SimpleQueue(src/utils/queue.ts:1) - RAG local vector (hash embedding 128-dim, cosine, chunk 500/50) at
src/tools/rag.tool.ts:1—rag_ingest(chunked +sendResourceListChanged),rag_search(topK, threshold),rag_list,rag_clear+docs://{id}resource - Web
src/tools/web.tool.ts:1—brave_search(mock if noBRAVE_API_KEY),tavily_search(mock),web_fetch(cached viadefaultCache,CACHE_TTL_MS) - GitHub
src/tools/github.tool.ts:1—github_search_repos,github_get_repo,github_get_issue(cached,GITHUB_TOKENfor rate limit) - Stack
docker-compose.yml:1(app + redis:7 + postgres:16 + qdrant:v1.12.4) with healthchecks - Demo
rag_ingest → rag_search → docs://E2E verified intests/integrations.test.ts:1(21 tests)
🏢 Scale & Enterprise (Phase 5 — v3.0) — NEW
- Multi-tenant
src/middleware/tenant.ts:1—X-Tenant-Idheader (or?tenant=),tenantMiddlewarerejects400whenTENANT_REQUIRED=trueand missing, tenant-scoped memorygetTenantMemory(tid)(isolated stores), namespaced cache keystenant:{id}:key, registrycreateTenant/deleteTenant - SSO OIDC
src/middleware/auth.ts:1—AUTH_MODE=oidc: Bearer JWT structural validation (3-part,exp,issvsOIDC_ISSUER,audvsOIDC_AUDIENCE), claims attached toreq.oidcClaims; production swaps in JWKS signature verification - Control plane
src/routes/controlplane.ts:1— CRUD/admin/tenants(POST/GET/PATCH/DELETE+ 409 dup/400 invalid-id),POST /admin/tenants/:id/rotate-key,GET /admin/tenants/:id/storeisolation inspection; admin-token protected - Runtime scale
src/utils/cluster.ts:1—initCluster()forksCLUSTER_WORKERS(default CPU-1), auto-restart on worker exit,CLUSTER_MODE=true; stateless +RedisEventStorefor true horizontal - Monitoring stack
docker-compose.override.yml:1— Prometheus (prometheus.ymlscrapesapp:3000/metrics) at :9090 + Grafana at :3001 - Tests
tests/v3_0.test.ts:1(15 tests: tenant extraction/isolation/required-400, OIDC token validation + issuer/audience + HTTP 401/200, control plane lifecycle 201/409/400/404/rotate-key/store-inspection/disabled-404, cluster no-op, compose services) — total 178
🔌 Ecosystem & DX (Phase 5 — v2.2)
- Plugin SDK
src/plugin/index.ts:1—definePlugin({name, version, register}),registerPlugin(server, plugin)(duplicate-tolerant),getRegisteredPlugins(), autosendToolListChangednotifications - Integrations
src/integrations/—slackPlugin(list/post/search),notionPlugin(search/get/create),linearPlugin(list/create/get) — all mocked without tokens (SLACK_TOKEN,NOTION_TOKEN); auto-registered insrc/server.ts:1 - Registry
smithery.yaml:1— Smithery config + MCP Registry nameio.github.ahmedalbanna/mcp-server-base,package.json:1mcpName/filesfields - Hybrid RAG
src/tools/rag.tool.ts:1—rag_searchmodesvector|bm25|hybrid(default hybrid, alpha 0.5), BM25 (k1=1.5, b=0.75) normalized + cosine re-rank; eval settests/eval/rag-eval.json(20 Q/A) with p@5 ≥0.8 verified intests/v2_2.test.ts:1 - Prompt Playground
src/routes/admin.ts:1—POST /admin/prompts/:name/previewrenders prompts server-side; playground UI in/admindashboard - Tests
tests/v2_2.test.ts:1(16 tests: Plugin SDK define/register/dup/tools callable, registry files, hybrid modes, eval p@5, playground preview/UI/count) — total 163
📈 Scale & Operability (Phase 5 — v2.0)
- Versioned MCP
v2.0.0(package.json:1,config.MCP_SERVER_VERSION) with instructions per minor (src/server.ts:1) - OTEL tracing/metrics (
src/utils/otel.ts:1) —createSpan/withSpan,incrementCounter/recordHistogram,getMetrics/getSpans, JSON export stub forOTEL_EXPORTER_OTLP_ENDPOINT,OTEL_ENABLEDflag - RedisEventStore (
src/utils/redisEventStore.ts:1) —EventStoreimpl withstoreEvent/replayEventsAfter, in-memory fallback,eventStoreFactory.create()for horizontal scale (EVENT_STORE_TYPE=memory|redis,REDIS_URL) - Admin UI (
src/routes/admin.ts:1) —GET /admin(HTML dashboard),/admin/tools|resources|prompts|metrics|spans|stores|health(JSON), protected viaADMIN_TOKEN(X-Admin-Token),ADMIN_ENABLEDflag - Tasks (
src/tools/tasks.tool.ts:1) — experimentaldelay_task(if SDK tasks available) + fallbackcreate_task/get_task/get_task_result(in-memory, polling),SimpleQueue/MemoryCacheinfra - Bench
k6/load.js:1—http_req_duration p(95)<100ms,stages10→50 VUs,checks >99%,npm run bench/bench:local - Compose
docker-compose.yml:1already includes redis/postgres/qdrant for scale - Tests:
tests/scale.test.ts:1(OTEL spans/metrics, RedisEventStore replay, cache TTL, queue, admin HTML/metrics/token/ready, tasks create/poll, version, k6 script) — 130 total (now 147 with v2.1) - Deploy ready for Fly.io/Cloud Run (stateless + RedisEventStore), GHCR via
release.yml, npm2.0.0
🔒 Hardening & Observability (Phase 5 — v2.1) — NEW
-
OTEL Real
src/observability/otel-real.ts:1—initOtel()dynamic import of@opentelemetry/sdk-node+OTLPTraceExporter(console fallback),OTEL_ENABLED+OTEL_EXPORTER_OTLP_ENDPOINT, gracefulshutdownon SIGTERM -
SLOs
src/observability/slo.ts:1—checkSlo()(memory/rag/cache/uptime,latencyMs),GET /health→{status, checks, uptime, version, otel, eventStore}+GET /ready→503if notok,GET /metrics→ Prometheustext/plainviaprom-client(mcp_http_requests_total,mcp_http_request_duration_ms,mcp_sessions_active) -
RBAC
src/middleware/rbac.ts:1—reader < writer < admin(X-Roleheader, JWT stub),TOOL_ROLESmap (31 tools),mcpRbacMiddlewareinspectstools/callbody →403ifreadertrieswrite_file/shell_execute,GET /admin→adminrequired -
Backup
src/utils/persistence.ts:1—saveBackup()/loadBackup()/scheduleSave()(500ms debounce) toALLOWED_ROOT/.backup.json(dynamicgetBackupFile()), logs Redis sync stub whenREDIS_URLset,loadBackup()at startup insrc/index.ts:1 -
Metrics
src/utils/metrics.ts:1—prom-clientRegistry+collectDefaultMetrics,httpRequestsTotal/httpRequestDuration/mcpToolCallsTotal/mcpSessionsActive,GET /metricshandler -
Tests
tests/v2_1.test.ts:1(13 tests: SLO checks, RBAChasRole/X-Role403/200,SLO health/metrics200 + Prometheus text, OTEL span, backup save/load +scheduleSaveviamemory_set,initOteldisabled/enabled) +tests/unit/rbac.test.ts:1(4 tests) — total 147 -
Logger stderr-safe, never logs secrets (redaction)
-
Zod → JSON Schema via SDK (
src/types.ts:1,src/tools/*.tool.ts) -
Timeout on fetch (10s) + structured errors
-
Graceful shutdown (
SIGINT/SIGTERM) -
Health (
GET /health) & ready (GET /ready) separate from MCP -
Stateless default (
sessionIdGenerator: undefined), stateful whenRESUMABILITY_ENABLED=true(src/index.ts:22) -
Type-safe, strict TS + ESLint flat + Prettier + husky + lint-staged
-
Coverage 85% lines / 70% branches enforced (
vitest.config.ts:1), 178 tests: unit + e2e HTTP/security/capabilities/integrations/scale/v2.1/v2.2/v3.0
📚 Docs
Full documentation in docs/:
- Architecture — diagram, module map, request lifecycle
- Configuration — every env var + validation rules
- API Reference — tools/resources/prompts/endpoints
- Security — auth, RBAC, OIDC, tenancy, hardening checklist
- Plugins — Plugin SDK guide + registry distribution
- Deployment — Docker, cluster/multi-replica scale, monitoring, k6
- Testing — suite map, patterns, CI
🤝 Contributing
See CONTRIBUTING.md — nvm use, npm test, add tool/resource/prompt, ensure lint/typecheck/test pass. See CODE_OF_CONDUCT.md.
📚 MCP Docs
- Spec: https://spec.modelcontextprotocol.io
- SDK: https://github.com/modelcontextprotocol/typescript-sdk
- Inspector: https://github.com/modelcontextprotocol/inspector
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。