cortex

cortex

An MCP-native agentic platform orchestrating planner/executor/critic agents over hybrid RAG with three-tier memory, budget enforcement, safety guardrails, and full observability. It exposes all capabilities as MCP tools, enabling natural-language control of document ingestion, retrieval-augmented generation, and multi-step AI workflows.

Category
访问服务器

README

Cortex

An MCP-native agentic platform: planner/executor/critic over hybrid RAG, with the budget, safety and observability layers a production deployment needs.

384 tests · 84% coverage, gate-enforced · runs offline against fakes · no service required to develop

Cortex combines MCP, multi-agent orchestration, hybrid RAG, three-tier memory, LLMOps, automated evaluation and safety guardrails in one codebase. What it is not is a system that has been run at scale: it has never served production traffic, and the load-testing and scale-out work is listed in LIMITATIONS.md rather than implied here.


Why Cortex

Most AI projects are tutorials. Cortex is built the way a senior AI engineer would build it for production:

  • MCP-native — all capabilities exposed as Model Context Protocol tools. Connect Claude Desktop, VS Code, or any MCP client to your running Cortex server in minutes.
  • Multi-LLM via LiteLLM — swap between OpenAI, Anthropic, Azure, Bedrock, or Vertex by changing one config value.
  • Full observability — every LLM call, agent step, and tool invocation is an OTEL span, visible in Arize Phoenix and Grafana.
  • Automated evaluation — a regression suite on a 6-hourly schedule with a faithfulness threshold. Ragas is an optional extra and currently fails to import against the released langchain-community, so the default install scores with an LLM-as-judge fallback — LIMITATIONS.md says which you are getting.
  • Budget enforcement — a per-run cost ceiling checked before every model call, against a Redis ledger shared across async tasks and workers.
  • Rate limiting that runs before routing — so requests that 404 or 422 are metered too. Those are cheaper for an attacker to generate than valid ones.

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         MCP Clients                                  │
│         Claude Desktop │ VS Code │ Custom Agents │ REST API          │
└─────────────────┬───────────────────────────────────────────────────┘
                  │ MCP / HTTP
┌─────────────────▼───────────────────────────────────────────────────┐
│                    Cortex API Gateway (FastAPI)                        │
│              Auth │ Rate Limiting │ SSE Streaming │ /metrics          │
└─────────────────┬───────────────────────────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────────────────────────┐
│                 LangGraph Agent Orchestration                         │
│        load_memory → planner → executor → critic → save_memory       │
└──────┬──────────┬──────────────┬──────────────┬───────────────────-─┘
       │          │              │              │
  ┌────▼───┐ ┌───▼────┐ ┌──────▼────┐ ┌──────▼──────┐
  │  MCP   │ │  RAG   │ │  Memory  │ │   Safety    │
  │ Server │ │Pipeline│ │  3-Tier  │ │ Guardrails  │
  └────┬───┘ └───┬────┘ └──────┬────┘ └─────────────┘
       │          │              │
┌──────▼──────────▼──────────────▼──────────────────────────────────-─┐
│                      LiteLLM Router                                   │
│         OpenAI │ Anthropic │ Azure │ Bedrock │ Vertex │ Ollama        │
└───────────────────────────────────────────────────────────────────-──┘
       │          │              │
  ┌────▼──┐  ┌───▼───┐  ┌──────▼──────────────────────────────────┐
  │Qdrant │  │ Redis │  │  Observability: OTEL + Phoenix + Grafana │
  └───────┘  └───────┘  └─────────────────────────────────────────┘

Quickstart (5 minutes)

Prerequisites

  • Python 3.10+ (CI tests 3.10 and 3.13)
  • Docker + Docker Compose
  • At least one LLM API key (OpenAI recommended for quickstart)

1. Clone and configure

git clone https://github.com/mrinmoyece/cortex
cd cortex
cp .env.example .env
# Edit .env — set OPENAI_API_KEY and SECRET_KEY at minimum.
# SECRET_KEY is what signs API tokens: leave it unset and one is generated
# per process, so every restart invalidates every token in flight.

2. Start all services

docker compose up -d

Services will be available at:

Service URL
Cortex API http://localhost:8000/docs
Cortex MCP http://localhost:8001 (compose sets MCP_TRANSPORT=http; stdio is the default elsewhere)
Arize Phoenix http://localhost:6006
Grafana http://localhost:3000 (admin / $GRAFANA_ADMIN_PASSWORD, default cortex)
Qdrant UI http://localhost:6333/dashboard
Prometheus http://localhost:9090

3. Ingest a document

curl -X POST http://localhost:8000/api/v1/ingest \
  -H "Authorization: Bearer $(python scripts/gen_token.py)" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your document content here", "metadata": {"source": "quickstart"}}'

4. Run an agent

curl -X POST http://localhost:8000/api/v1/runs \
  -H "Authorization: Bearer $(python scripts/gen_token.py)" \
  -H "Content-Type: application/json" \
  -d '{"goal": "Summarise the key points from the ingested documents"}'

5. Check on the run, or call a tool directly

export TOKEN=$(python scripts/gen_token.py)

# Poll the run you just created
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/api/v1/runs/<run_id>

# Call one MCP tool directly. The endpoint authenticates you and binds your
# identity to the call, so tools that read your memory work here and nowhere
# else over the network.
curl -X POST http://localhost:8000/api/v1/mcp/call \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tool": "search_knowledge", "arguments": {"query": "quickstart", "top_k": 3}}'

Reachable tools are MCP_HTTP_TOOL_ALLOWLIST. Anything else is a 404; arguments a tool cannot accept are a 422; a tool needing an identity it does not have is a 403.

6. Connect Claude Desktop

Add to your Claude Desktop claude_desktop_config.json:

{
  "mcpServers": {
    "cortex": {
      "command": "/path/to/your/venv/bin/cortex-mcp",
      "env": {
        "MCP_TRANSPORT": "stdio",
        "OPENAI_API_KEY": "sk-...",
        "SECRET_KEY": "your-32-plus-char-key"
      }
    }
  }
}

cortex-mcp is installed by pip install -e .; use its absolute path, because Claude Desktop does not inherit your shell's PATH or your .env. The transport must be stdio here — that is the default, and it is what Claude Desktop speaks.

query_memory reads memory belonging to a specific person, and an MCP transport carries no authentication to derive that from. Add "MCP_PRINCIPAL_USER_ID": "your-user-id" to the env block above to declare whose memory this single-user process may read; without it the tool refuses. The declaration is honoured on stdio only — on the HTTP transport it is ignored, because one identity shared across every caller on a port is not an identity. See docs/MCP.md.

Running without Docker

The stack needs Redis and Qdrant; everything else runs in-process.

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env            # set OPENAI_API_KEY and SECRET_KEY

docker run -d -p 6379:6379 redis:7-alpine
docker run -d -p 6333:6333 qdrant/qdrant:latest

cortex-api                      # http://localhost:8000/docs
cortex-mcp                      # separate shell; MCP_TRANSPORT=http to reach it over HTTP
cortex-worker                   # separate shell; only if you use the queues

Each of those is a console script installed by pip install -e .. Verify the API is up before going further:

curl http://localhost:8000/health
# {"status":"ok","service":"cortex","environment":"local", ...}

Then mint a token for the curl examples above:

export TOKEN=$(python scripts/gen_token.py)
curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/api/v1/runs ...

The test suite needs none of this — Redis is faked and every network client is mocked, so pytest runs on a laptop with no services at all.


Project Structure

cortex/
├── src/cortex/
│   ├── config.py              # Pydantic Settings — all configuration
│   ├── exceptions.py          # Domain exception hierarchy
│   ├── logging_config.py      # Structlog structured logging
│   ├── mcp/
│   │   ├── server.py          # FastMCP server — MCP tool definitions
│   │   └── client.py          # MCP client for agent tool calls
│   ├── llm/
│   │   ├── router.py          # LiteLLM wrapper with retry, fallback, budget
│   │   ├── cost_tracker.py    # Per-run token cost accounting (Redis)
│   │   └── cache.py           # Semantic cache (Qdrant)
│   ├── graph/
│   │   ├── state.py           # LangGraph state schema (CortexState)
│   │   └── cortex_graph.py     # Graph topology, nodes, routing functions
│   ├── agents/
│   │   ├── planner.py         # Goal → task list decomposition
│   │   ├── executor.py        # Task execution via MCP tools
│   │   ├── critic.py          # Output quality evaluation
│   │   └── memory_agent.py    # Three-tier memory: working, episodic, semantic
│   ├── rag/
│   │   └── pipeline.py        # Ingest, chunk, embed, hybrid search, rerank
│   ├── safety/
│   │   ├── middleware.py      # PII scanner, injection detector, guardrails
│   │   └── moderation.py      # Layered input/output moderation, fails closed
│   ├── obs/
│   │   ├── metrics.py         # Prometheus metrics
│   │   └── tracing.py         # OTEL spans + Phoenix setup
│   ├── eval/
│   │   ├── ragas_runner.py    # RAG quality, with an LLM-judge fallback
│   │   └── agent_eval.py      # Agent-level metrics from run structure
│   ├── workers/
│   │   └── celery_app.py      # Background tasks and the beat schedule
│   └── api/
│       ├── main.py            # FastAPI app — all HTTP endpoints
│       ├── auth.py            # JWT authentication
│       └── ratelimit.py       # Token-bucket limiter, before routing
├── config/
│   └── rails/                 # NeMo Guardrails Colang policies
├── obs/
│   ├── prometheus.yml         # Prometheus scrape config
│   └── grafana/               # Grafana dashboard JSON + provisioning
├── perf/
│   ├── benchmark.py           # Edge latency gate — exits non-zero on breach
│   └── locustfile.py          # Load profile for a deployed instance
├── scripts/
│   ├── audit.py               # Project-scoped dependency audit
│   └── gen_token.py           # Local API token for the curl examples
├── tests/                     # Pytest test suite (80%+ coverage required)
├── deploy/k8s/                # Kubernetes manifests (unapplied — see LIMITATIONS)
├── docs/adr/                  # Architecture Decision Records
├── docker-compose.yml         # Full local stack
├── Dockerfile                 # Multi-stage production image
├── pyproject.toml             # Dependencies and tooling config
└── .env.example               # All required environment variables

Documentation

Document What it covers
ARCHITECTURE.md Full system design, data flows, design decisions
MCP.md MCP server, tool definitions, connecting clients
AGENTS.md Agent design, prompts, tool access, failure modes
RAG.md Ingestion, chunking strategy, hybrid search, evaluation
MEMORY.md Three-tier memory design and retrieval strategy
LLMOPS.md Observability setup, metrics catalogue, dashboards
GUARDRAILS.md Safety policies, PII handling, injection defence
EVALUATION.md Ragas metrics, regression testing, baseline scores
COST.md Token tracking, caching, budget enforcement
DEPLOYMENT.md Docker Compose, Azure/AWS cloud deployment
TESTING.md Test strategy, coverage targets, eval tests
PERFORMANCE.md What the latency gate measures, and what it does not
LIMITATIONS.md Where this stops being production-ready, in detail
SECURITY.md Reporting a vulnerability, and what is in scope
ADR/001 Why MCP over custom REST for tool exposure
ADR/002 State schema design decisions
ADR/003 Multi-LLM routing strategy
ADR/004 Memory architecture

Running Tests

pip install -e ".[dev]"
pytest                    # Full suite with coverage
pytest tests/test_rag/    # RAG tests only
pytest -k "test_planner"  # Single test

Coverage target: 80% minimum (enforced in CI). No service is required: Redis is faked, Qdrant and every provider are mocked.

Everything CI runs, in the same order:

make gate                 # lint, types, tests, bandit, dependency audit, perf

Or individually:

make lint                 # ruff check + ruff format --check
make types                # mypy (strict, and blocking in CI)
make test                 # pytest with the coverage gate
make security             # bandit -r src -ll
make audit                # pip-audit, scoped to Cortex's dependency closure
make perf                 # edge latency budgets

make audit audits the versions installed in your environment. On a long-lived machine those can be far older than a clean install would pick, which reports advisories you cannot act on from this repository; make audit-fresh resolves the tree from the index instead, which is what CI installs.


Contributing

See CONTRIBUTING.md. All PRs require:

  • Tests for new code
  • Updated ADR if architecture changes
  • The full gate to pass (make gate)

tests/eval/ holds the regression cases (regression_cases.json) consumed by cortex.eval.ragas_runner.run_regression_suite, not pytest tests. The evaluation harness's own tests are in tests/test_eval/.


What was wrong with this codebase, and how it was found

Cortex was generated complete — 77 files, eight layers, an 80% coverage gate — and had never been executed. Not once. Every defect below was found by making it run.

Severity Defect Why nothing caught it
Blocking The test suite could not be collected. Sixteen modules called get_settings() at import time; SECRET_KEY is unset on a clean machine, so import cortex.obs.metrics raised ValidationError before any test started. --cov-fail-under=80 was gating a suite that had never run.
Blocking FastMCP(version=...) is not a valid API. Two different classes share the name; the code imported the one from the mcp SDK and passed it the other one's arguments. Both packages are declared in pyproject.toml, which made the mistake look plausible.
Blocking The executor never passed its tool schemas to the model. It fetched them into a local variable and dropped them. Under a mocked router the loop still "worked" — the mock returns tool calls whether or not any tools were offered. Ruff found it, as an unused variable.
Blocking Four tests required a live Redis. The mock_router fixture patched cortex.llm.router.get_router, but agents bind that name at import, so the patch rebound a name nobody read. The fixture looked correct and was a no-op.
High Rate limiting did not exist. api_rate_limit_per_minute was configured, documented, and read by nothing. A control an operator believes they have.
High query_data was a stub advertising itself as a working tool. It returned an empty result set with a note, so an agent would "query the data", get zero rows, and confidently report no matching records. A tool that fakes success is worse than an absent one — the absent one can be planned around.
High tenant_id was carried in state, described in the docs, and used by neither memory tier. Safe only while user ids are globally unique, which they are not when they come from tenant-local identity providers.
High A Gauge labelled by run_id. Every run minted a permanent time series. The classic cardinality mistake: invisible until Prometheus falls over weeks later, during an unrelated incident.
Medium The graph's cost guard read >= 2.0 instead of the configured ceiling — which happens to equal the default, so it looked right and ignored its own configuration.
Medium A memory-write failure crashed a run that had already produced its answer. Losing the memory write is a degradation. Losing the answer is a bug.
Medium Document.id was uuid4() next to an unused doc_hash property, so re-ingesting an unchanged file duplicated the corpus instead of upserting over it.
Medium An unexpected exception type skipped the LLM fallback entirely and propagated raw, so the documented failure mode was not the one callers got.
Low Six dependencies declared and never imported; a Postgres service nothing connected to; MemoryError shadowing the builtin.

The reason this table is here rather than quietly fixed: the gap between "generated" and "working" is where all the engineering lives, and every row is a question I can answer in depth.

Second pass: what a full audit found after it ran

The first pass made Cortex run. A second, repository-wide review asked a different question — what breaks the first time this meets a second replica, a real client, or an attacker — and found these.

Severity Defect Why nothing caught it
Blocking docker-compose.yml was not valid YAML. The depends_on block under x-cortex-common was missing its first mapping key, so docker compose up failed before starting anything. The documented five-minute quickstart could never have worked. Nothing in CI parses the compose file, and a human reads the shape rather than the indentation.
Blocking POST /api/v1/mcp/call could not succeed. It awaited get_tool() — a coroutine in FastMCP 3 — and then reached for .fn on it. The endpoint had no test.
Blocking The graph interrupted before the critic unconditionally, with no resume endpoint anywhere, so every run stopped half-done and was reported as COMPLETED. The test asserted the interrupt was configured, which it was. It never ran a graph to the end.
Blocking The semantic cache called AsyncQdrantClient.search, removed in qdrant-client 1.13. Every cache read raised AttributeError against a real server. Tests mocked .search, so the mock kept an API alive that the library had deleted.
Critical The cache embedded a SHA-256 digest, not the prompt. Cosine similarity between hex digests is noise, so the "semantic" cache was neither semantic nor safe — and it had no tenant scope, so a near-collision served one tenant's answer to another. A cache that returns wrong answers looks exactly like a cache that returns right ones until someone reads a hit.
Critical query_memory took user_id as a tool argument. The model chose whose memory to read. The parameter looked like plumbing rather than an authorisation decision.
High execute_code ran arbitrary Python in the API container, enabled, advertised to the planner, and documented as "sandboxed". It has a timeout and an output cap, which reads like isolation and is not.
High The run store was an unbounded dict. Every run ever accepted stayed in memory for the life of the process — a memory leak with a public trigger. Nothing evicts, so nothing fails, until it does.
High Rate-limit buckets were keyed by hash(token) & 0xFFFFFFFF. 32 bits is ~77k tokens to a coin-flip collision, and colliding principals share a bucket, so one caller can exhaust another's allowance. hash() is also per-process randomised. Both bugs are invisible at the scale a test runs at.
High Sparse retrieval ignored the filters dense retrieval enforced, so a filtered hybrid search returned unfiltered BM25 results — including, in a multi-tenant deployment, other tenants'. The dense half was filtered, so spot checks looked right.
High Document.id was a SHA-256 hex digest. Qdrant only accepts UUIDs or unsigned integers as point IDs, so ingestion rejected every point. The RAG tests never reached a real Qdrant.
Medium Presidio and the regex fallback were mutually exclusive, so installing Presidio removed credit-card and UK NINO detection — its English config ships neither recogniser. Its default entity set also classified "annual" as a date and redacted it out of user goals. Three safety tests failed only when Presidio happened to be installed.
Medium agent_runs_total.labels(status=RunStatus.COMPLETED) produced the label RunStatus.COMPLETED rather than completed, so the dashboards queried a series that did not exist. Prometheus accepts any string as a label value.
Medium /metrics was public, exposing model names, spend totals and run volumes on the same port as the API. Metrics endpoints are conventionally open, so nobody asks what is in them.
Medium A memory-tier failure aborted the run before the planner had a chance to work without memory. Degradation was written as an exception path.
Medium completion_cost raising discarded a completion that had already been paid for — and it raises for any model litellm has no pricing for, which is every model newer than the pinned release. Pricing lookups are assumed infallible because they are local.
Medium The worker and beat pods ran as root with a writable root filesystem and no probes, next to an API pod that was fully hardened. The hardening was written once, for the deployment someone was looking at.
Medium Prometheus scraped a Redis exporter that does not exist and loaded a rule file that was never mounted, so every alert in obs/alerts.yaml was inert. An alert that never fires and an alert that cannot fire look identical.
Medium The image had no CMD. docker run cortex exited immediately. Every compose service named a command explicitly.
Low python-jose pulled in ecdsa and its unfixed timing-attack advisory; presidio-anonymizer was declared, unused, and pinned cryptography<49; ragas — which does not import at all against the released langchain-community — sat in the runtime closure dragging diskcache and pillow advisories with it. pip-audit --strict audited the whole machine, so its output was noise and got read as noise.
Low The performance gate's rate_limited row called RateLimiter.check() in-process and reported 0.00ms against a 20ms budget. A budget that cannot be breached is decoration.

mypy --strict went from 83 errors in 21 files, marked advisory in CI, to zero and blocking.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选