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
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
E2B
Using MCP to run code via e2b.