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
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。