{
  "markdown": "# architecture-pattern-mcp\n\n[![CI](https://img.shields.io/github/actions/workflow/status/olk/architecture-pattern-mcp/ci.yml?branch=main)](https://github.com/olk/architecture-pattern-mcp/actions)\n[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)\n[![M8ven Score](https://m8ven.ai/badge/mcp/olk-architecture-pattern-mcp-1x6yt9)](https://m8ven.ai/mcp/olk-architecture-pattern-mcp-1x6yt9)\n\nAn MCP (Model Context Protocol) server that provides architecture design expertise to AI coding agents. Given a requirements string and a domain, it analyses the problem, selects matching architecture patterns (from 40 built-in patterns), generates a concrete architecture design with components, relationships, API contracts, data models, and event contracts, and evaluates it against quality attributes (maintainability, scalability, reliability, security, performance).\n\n---\n\n## Table of Contents\n\n- [⚡ Quickstart](#-quickstart)\n- [🔌 Connect Your Agent](#-connect-your-agent)\n  - [Claude Code](#claude-code)\n  - [OpenCode](#opencode)\n  - [Codex CLI](#codex-cli)\n- [🧑‍🏫 SKILL for AI Agents](#-skill-for-ai-agents)\n- [🧪 Use the Tools](#-use-the-tools)\n  - [Design your first architecture](#design-your-first-architecture)\n  - [Explore the pattern catalog](#explore-the-pattern-catalog)\n- [🛠️ Tools at a Glance](#️-tools-at-a-glance)\n- [📖 Pattern Catalog](#-pattern-catalog)\n- [Install Alternatives](#install-alternatives)\n  - [Docker (manual)](#docker-manual)\n  - [Local Development (uv)](#local-development-uv)\n- [Configuration](#configuration)\n- [Structured Reasoning (shannonthinking / code-reasoning)](#structured-reasoning-shannonthinking--code-reasoning)\n- [Extending with Custom Patterns](#extending-with-custom-patterns)\n- [Long-running tools & timeouts](#long-running-tools--timeouts)\n- [Troubleshooting](#troubleshooting)\n- [Building & Development](#building--development)\n- [Publishing](#publishing)\n- [systemd Service (Linux)](#systemd-service-linux)\n- [License](#license)\n\n---\n\n## ⚡ Quickstart\n\n```bash\n# 1. Clone\ngit clone https://github.com/architecture-pattern/architecture-pattern-mcp.git\ncd architecture-pattern-mcp\n\n# 2. Add your API key\nexport GENERATOR_API_KEY=your_key_here\n\n# 3. Start (Docker builds + starts everything)\ndocker compose -f docker/docker-compose.yml up --build\n\n# 4. Demo\nmake client\n```\n\nServer starts on **streamable-http** at `http://localhost:8060/mcp` (dev compose host port; systemd uses 8050). Then connect your agent below.\n\n---\n\n## 🔌 Connect Your Agent\n\n### Claude Code\n\n```bash\n# Install (one-time)\nuv pip install -e .\n\n# Run as stdio subprocess — pass API key via env\nclaude mcp add architecture-pattern \\\n  -e GENERATOR_API_KEY=your_key \\\n  -e GENERATOR_PROVIDER=openai \\\n  -- architecture-pattern-mcp --transport stdio\n```\n\nOr add to your project for the whole team:\n\n```bash\nclaude mcp add --scope project architecture-pattern \\\n  -e GENERATOR_API_KEY=your_key \\\n  -- architecture-pattern-mcp --transport stdio\n```\n\n### OpenCode\n\nOpenCode uses HTTP transport. Start the server first, then configure opencode:\n\n```bash\n# Terminal 1: start the server\ndocker compose -f docker/docker-compose.yml up --build\n# or locally:\nuv run python -m src.main --port 8050\n\n# Terminal 2: add to ~/.config/opencode/opencode.json\n```\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"mcp\": {\n    \"architecture-pattern\": {\n      \"type\": \"remote\",\n      \"url\": \"http://localhost:8060/mcp\"\n    }\n  }\n}\n```\n\n> **Note:** `GENERATOR_API_KEY` is read from the server's config file (`~/.config/architecture-pattern-mcp/config.json`), not from opencode's environment.\n\n### Codex CLI\n\n```bash\n# Install (one-time)\nuv pip install -e .\n```\n\nAdd to `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.architecture-pattern]\ncommand = \"architecture-pattern-mcp\"\nargs = [\"--transport\", \"stdio\"]\n\n[mcp_servers.architecture-pattern.env]\nGENERATOR_API_KEY = \"your_key\"\nGENERATOR_PROVIDER = \"openai\"\n```\n\nOr via CLI:\n\n```bash\ncodex mcp add architecture-pattern \\\n  -e GENERATOR_API_KEY=your_key \\\n  -- architecture-pattern-mcp --transport stdio\n```\n\n---\n\n## 🧑‍🏫 SKILL for AI Agents\n\nAI coding agents (Claude Code, OpenCode, Codex CLI) can load a SKILL that teaches them how and when to use this server's tools — including timeout-aware entry-point selection, output interpretation, and the full workflow recipe.\n\nThe SKILL lives in `skills/architecture-pattern-mcp/`:\n\n```\nskills/architecture-pattern-mcp/\n├── SKILL.md                 # Discovery, critical rules, decision guide\n└── references/\n    ├── tools.md             # All 9 tool signatures and output schemas\n    └── workflows.md         # 4 worked examples, 4 prompts, best practices\n```\n\n**For agents that support file-based skills** (OpenCode, Claude Code): point the agent's skill loader at `skills/architecture-pattern-mcp/SKILL.md`. The skill tells the agent:\n\n- Which tool to use based on client type and timeout budget\n- How to phrase `requirements`, `domain`, and `style` as separate structured arguments\n- How to interpret `final_quality_score`, `attempts > 1`, and `evaluation.recommendations`\n- When to use the async job trio vs `design_architecture` directly\n\n---\n\n## Use the Tools\n\nAll tools accept `requirements` (free text) and `domain` (e.g. `data-processing`, `microservices`, `e-commerce`) as arguments. The examples below show the exact tool call shape so you can use them in any MCP client or API consumer.\n\n### Try each tool\n\nIn Claude Code (or any MCP client), paste the natural-language instruction:\n\n```\nBuild a scalable ETL pipeline for IoT sensor data: ingest 10k events/sec\nfrom Kafka, parse JSON, enrich with geolocation from Redis, write to InfluxDB\nand S3.\n```\n\nYour agent calls `design_architecture` internally. The server returns a full architecture design: components (Kafka source, JSON parser filter, geolocation enricher, InfluxDB sink, S3 sink), quality attribute scores (scalability: 9.1, maintainability: 8.2, …), and specific recommendations.\n\n**Or call tools directly** from your agent:\n\n```\nCall analyze_architecture with:\n  requirements: \"Real-time data processing pipeline for 10k events/sec IoT sensor data\"\n  domain: \"data-processing\"\n\nCall generate_architecture with:\n  requirements: \"ETL pipeline: Kafka → JSON parse → Redis geo-enrich → InfluxDB + S3\"\n  domain: \"data-processing\"\n  selected_patterns: [\"pipe-and-filter\"]\n\nCall evaluate_architecture with:\n  architecture: { ... paste a design dict here ... }\n  criteria: \"scalability, reliability\"\n\nCall list_architecture_patterns()  # all 40 patterns\nCall list_architecture_patterns(category=\"messaging\")  # filter by category\nCall get_architecture_pattern(name=\"event-driven\")   # full pattern JSON\n```\n\n### Async job pattern: `submit_architecture_design_job` + `get_architecture_design_status`\n\nONLY for clients with short request timeouts (Cursor, Claude Desktop, TS-SDK). The default is `design_architecture` with heartbeat defence. `submit_architecture_design_job` returns a `job_id` immediately; poll `get_architecture_design_status` until done:\n\n```\n# Step 1: start the job\nCall submit_architecture_design_job with:\n  requirements: \"ETL pipeline for IoT: Kafka → JSON → Redis geo-enrich → InfluxDB + S3\"\n  domain: \"data-processing\"\n\n# Step 2: poll every 10-30 seconds\nCall get_architecture_design_status with:\n  job_id: \"<job_id from step 1>\"\n\n# → status is \"pending\" | \"running\" | \"completed\" | \"failed\" | \"cancelled\"\n# When status is \"completed\", the full design is in result.design\n# When status is \"failed\", the error is in result.error\n```\n\nIn Python (via the MCP HTTP API directly — see `examples/architecture_client_async.py`):\n\n```python\nimport asyncio, aiohttp\n\nSERVER = \"http://localhost:8060/mcp\"\nPOLL_EVERY = 15  # seconds\n\nasync def main():\n    async with aiohttp.ClientSession() as sess:\n        # Start\n        async with sess.post(SERVER, json={\n            \"jsonrpc\": \"2.0\",\n            \"method\": \"tools/call\",\n            \"params\": {\n                \"name\": \"submit_architecture_design_job\",\n                \"arguments\": {\n                    \"requirements\": \"ETL pipeline for IoT: Kafka → JSON → Redis → InfluxDB + S3\",\n                    \"domain\": \"data-processing\",\n                }\n            },\n            \"id\": 1\n        }) as resp:\n            job_id = (await resp.json())[\"result\"][\"content\"][0][\"data\"][\"job_id\"]\n\n        print(f\"Job started: {job_id}\")\n\n        # Poll\n        while True:\n            await asyncio.sleep(POLL_EVERY)\n            async with sess.post(SERVER, json={\n                \"jsonrpc\": \"2.0\",\n                \"method\": \"tools/call\",\n                \"params\": {\"name\": \"get_architecture_design_status\", \"arguments\": {\"job_id\": job_id}},\n                \"id\": 2\n            }) as resp:\n                result = (await resp.json())[\"result\"][\"content\"][0][\"data\"]\n                print(f\"  status={result['status']}\")\n                if result[\"status\"] in (\"completed\", \"failed\", \"cancelled\"):\n                    break\n\n        print(result.get(\"result\", result))  # full design when completed\n```\n\nSee `examples/architecture_client_async.py` for the complete runnable example. Run it with:\n\n```bash\ndocker compose -f docker/docker-compose.yml up --build   # Terminal 1\nmake client-async                                      # Terminal 2\n```\n\n### Explore the pattern catalog\n\n```\nCall list_architecture_patterns() with no filters to see all patterns.\n```\n\nOr get details on a specific pattern:\n\n```\nShow me details about the event-driven architecture pattern.\n```\n\n---\n\n## 🛠️ Tools at a Glance\n\n| Tool | Description |\n|---|---|\n| `analyze_architecture` | Analyse requirements and domain → recommended style, patterns, quality metrics. *Long-running (LLM call). Not idempotent.* |\n| `generate_architecture` | Generate an architecture design from requirements and selected patterns. *Long-running (LLM call). Not idempotent.* |\n| `evaluate_architecture` | Score an existing design against quality attributes. *Long-running (LLM call). Not idempotent.* |\n| `design_architecture` | Default tool for full architecture design (analyse → generate → evaluate → refine, up to 3 attempts). *Long-running (5–10 min); use this unless your client has a short request timeout.* |\n| `submit_architecture_design_job` | Start a background design job and return a `job_id` immediately. **ONLY for clients with short request timeouts** (Cursor, Claude Desktop, TS-SDK). For other clients use `design_architecture`. Poll `get_architecture_design_status` every 10–30 s. |\n| `get_architecture_design_status` | Poll job status. Returns the current status, progress message, and the full design output when `completed`. |\n| `cancel_architecture_design` | Cancel a running job (best-effort; takes effect at the next pipeline stage boundary; may take up to one LLM call). |\n| `list_architecture_patterns` | List all 40 patterns; filter by `category` and/or `domain` |\n| `get_architecture_pattern` | Get full JSON for a specific pattern by name |\n\n\n**Domain and Style are structured parameters** — pass them as separate tool arguments, not embedded in the requirements text.\n\nExample prompts:\n\n```\nBuild a scalable distributed system for processing IoT sensor data with\n100k events per second throughput, written in Python, deployed on Kubernetes.\n```\n\n```\nDesign an architecture for an e-commerce platform handling flash-sales events.\nDomain: e-commerce. Style: microservices.\n```\n\n```\nShow me details about the blackboard pattern.\n```\n\n```\nCall design_architecture with:\n  requirements: \"ETL pipeline for IoT: Kafka → JSON → Redis geo-enrich → InfluxDB + S3\"\n  domain: \"data-processing\"\n```\n\n---\n\n## 💬 Prompts\n\nThis server also exposes four user-invoked workflow prompts (slash commands\nin MCP clients). Unlike tools, the LLM does not autonomously invoke prompts —\nthe user selects one and fills in its arguments. Each prompt encodes a tested\ntool-orchestration recipe.\n\n| Prompt | Args | What it does |\n|---|---|---|\n| `/design_architecture_workflow` | requirements\\* | Full analyze → generate → evaluate pipeline |\n| `/explore_pattern_catalog` | `domain`, `category` | Live catalog discovery with embedded pattern names |\n| `/evaluate_my_architecture` | `focus` | Guide evaluation criteria + finding prioritisation |\n| `/compare_architecture_styles` | style_a\\*, style_b\\*, requirements\\* | Two designs side-by-side; ~2× token cost |\n\n\\* = required argument\n\n### Tool-only clients\n\nClients that only support the tools protocol (no native `prompts/list` or `prompts/get`) can access all four workflow prompts via the generated `list_prompts` and `get_prompt` tools, which route through the server's middleware chain exactly as native prompt calls do.\n\n---\n\n## 📖 Pattern Catalog\n\n### Via MCP tools (recommended — works in all clients)\n\n```\nlist_architecture_patterns()                                  # all 40 patterns\nlist_architecture_patterns(category=\"messaging\")               # filter by category\nlist_architecture_patterns(domain=\"microservices\")            # filter by domain\nget_architecture_pattern(name=\"event-driven\")                 # full pattern JSON\n```\n\nValid `category` values: `messaging`, `structural`, `cloud`, `data`, `ai_cognitive`, `specialized`, `api_gateway`, `coordination`, `dataflow`, `presentation`.\n\n### Via MCP resources\n\n```\nmcp_list_resources(server=\"architecture-pattern\")\nmcp_read_resource(server=\"architecture-pattern\", uri=\"pattern://microservices\")\n```\n\n### Pattern JSON structure\n\nEach pattern includes: `name`, `category`, `context`, `benefits`, `tradeoffs`, `quality_attributes` (scalability/maintainability/reliability/security/performance/simplicity, scores 1–10), `suitable_domains`, `component_types`, `technology_stack`, `design_principles`, `best_practices`.\n\n---\n\n## Install Alternatives\n\n### Docker (manual)\n\n```bash\n# Build the image\nmake docker-build\n\n# Run with your API key\nMINIMAXAI_API_KEY=your_key docker compose -f docker/docker-compose.yml up -d\n```\n\n### Local Development (uv)\n\n**Prerequisites:** Python 3.12+, [uv](https://github.com/astral-sh/uv)\n\n```bash\n# Install\nmake install\n\n# Configure\ncp config/config.json ~/.config/architecture-pattern-mcp/config.json\n# Edit ~/.config/architecture-pattern-mcp/config.json and set your GENERATOR_API_KEY\n\n# Run the server\nuv run python -m src.main --transport stdio              # for Claude Code / Codex\nuv run python -m src.main --port 8050                    # for OpenCode (HTTP, default)\n```\n\nOr use the installed console script (after `make install`):\n\n```bash\narchitecture-pattern-mcp --transport stdio\n```\n\nThe TEI embedder (Qwen3-Embedding-0.6B) is required for domain-scoped pattern retrieval. Without it, the server falls back to the default pattern. Docker compose starts it automatically; local users must run it separately on port 8080.\n\nThe retrieval indexes (FAISS + BM25) are built at server startup so a misconfigured or unreachable TEI sidecar prevents startup (fail-fast) rather than breaking the user's first design request. Docker compose's `service_healthy` dependency ordering guarantees TEI is ready before the app starts.\n\n---\n\n## Configuration\n\n### config.json\n\nThe server reads `~/.config/architecture-pattern-mcp/config.json` (override with `--config-path`):\n\n```json\n{\n  \"generator\": {\n    \"provider\": \"openai\",\n    \"config\": {\n      \"model\": \"gpt-4o-mini\",\n      \"base_url\": \"https://api.openai.com/v1\",\n      \"api_key\": \"{env:GENERATOR_API_KEY}\",\n      \"temperature\": 0.1,\n      \"top_p\": 1.0,\n      \"top_k\": 20\n    }\n  },\n  \"embedder\": {\n    \"provider\": \"tei\",\n    \"config\": {\n      \"base_url\": \"http://127.0.0.1:8080\"\n    }\n  },\n  \"retrieval\": {\n    \"bm25_top_k\": 0,\n    \"dense_top_k\": 0,\n    \"top_k_patterns\": 5,\n    \"min_quality_score\": 50.0\n  },\n  \"pattern_directory\": \"~/.config/architecture-pattern-mcp/pattern\",\n  \"transport\": \"streamable-http\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 8050,\n  \"logging_level\": \"INFO\",\n  \"logging_format\": \"json\"\n}\n```\n\n`{env:VAR:-default}` syntax expands environment variables at load time.\n\n### Generator LLM (LlamaIndex LiteLLM)\n\nThe generator LLM is accessed through the **LlamaIndex LiteLLM integration** ([`llama-index-llms-litellm`](https://docs.llamaindex.ai/en/stable/examples/llm/litellm/)). All provider settings therefore follow **LiteLLM's model syntax**: `<provider>/<model>` (e.g. `openai/gpt-4o-mini`, `anthropic/claude-sonnet-4-5`, `openrouter/minimax/minimax-m2`).\n\nThe server composes the LiteLLM model string from your configuration as `generator.provider` + `generator.config.model`:\n\n| Config / env | Example | Resulting LiteLLM model string |\n|---|---|---|\n| `provider: \"openai\"`, `model: \"gpt-4o-mini\"` | `GENERATOR_PROVIDER=openai`, `GENERATOR_MODEL=gpt-4o-mini` | `openai/gpt-4o-mini` |\n| `provider: \"anthropic\"`, `model: \"claude-sonnet-4-5\"` | `GENERATOR_PROVIDER=anthropic`, `GENERATOR_MODEL=claude-sonnet-4-5` | `anthropic/claude-sonnet-4-5` |\n| `provider: \"openrouter\"`, `model: \"minimax/minimax-m2\"` | `GENERATOR_PROVIDER=openrouter`, `GENERATOR_MODEL=minimax/minimax-m2` | `openrouter/minimax/minimax-m2` |\n\nIf the configured model already contains a provider prefix (e.g. `openai/gpt-4o-mini`), that prefix is stripped and replaced by the configured `provider`.\n\n- **Provider list, model names, and the exact `<provider>/<model>` syntax:** [LiteLLM Providers documentation](https://docs.litellm.ai/docs/providers)\n- Custom/OpenAI-compatible endpoints (proxies, vLLM, Ollama, …): set `GENERATOR_BASE_URL` (`generator.config.base_url`) — it is passed as the LiteLLM `api_base`\n- `GENERATOR_API_KEY` is passed as the LiteLLM `api_key`; `temperature`, `top_p`, `top_k`, and `stream` map to the corresponding LiteLLM parameters\n\n### Key environment variables\n\n| Variable | Default | Description |\n|---|---|---|\n| `GENERATOR_API_KEY` | *(required)* | API key for your LLM provider (passed to LiteLLM as `api_key`) |\n| `GENERATOR_PROVIDER` | `openai` | LiteLLM provider prefix: `openai`, `anthropic`, `openrouter`, … — see [LiteLLM Providers](https://docs.litellm.ai/docs/providers) |\n| `GENERATOR_BASE_URL` | `https://api.openai.com/v1` | API base URL (passed to LiteLLM as `api_base`) |\n| `GENERATOR_MODEL` | `gpt-4o-mini` | Model name; final model string is `<GENERATOR_PROVIDER>/<GENERATOR_MODEL>` (LiteLLM syntax) |\n| `GENERATOR_TEMPERATURE` | `0.1` | Sampling temperature |\n| `GENERATOR_TOP_P` | `1.0` | Top-p sampling |\n| `GENERATOR_TOP_K` | `20` | Top-k sampling |\n| `GENERATOR_STREAM` | `false` | Enable streaming responses |\n| `EMBEDDER_PROVIDER` | `tei` | Embedder provider |\n| `EMBEDDER_BASE_URL` | `http://127.0.0.1:8080` | TEI embedder URL |\n| `EMBEDDER_BATCH_SIZE` | `16` | Embedding batch size |\n| `EMBEDDER_QUERY_INSTRUCTION` | *(empty)* | Query instruction prefix |\n| `EMBEDDER_TEXT_INSTRUCTION` | *(empty)* | Text instruction prefix |\n| `RETRIEVAL_BM25_TOP_K` | `0` | BM25 stage-1 recall cap (0=full corpus) |\n| `RETRIEVAL_DENSE_TOP_K` | `0` | Dense stage-1 recall cap (0=full corpus) |\n| `RETRIEVAL_DENSE_WEIGHT` | `0.7` | **Stage-1 fusion leg weight** on the dense leg. Pairs with `RETRIEVAL_BM25_WEIGHT`; both must be > 0 and sum to 1.0 (±1e-3, startup validation). Either weight < 0.05 logs a startup warning. Distinct from the Stage-2 selection blend weights (`RETRIEVAL_ANALYSIS_BLEND_WEIGHT` / `RETRIEVAL_FUSION_BLEND_WEIGHT`). Note: config keys unknown to an older image fail fast at startup. |\n| `RETRIEVAL_BM25_WEIGHT` | `0.3` | **Stage-1 fusion leg weight** on the BM25 leg. See `RETRIEVAL_DENSE_WEIGHT`. |\n| `RETRIEVAL_TOP_K_PATTERNS` | `5` | Number of patterns to select |\n| `RETRIEVAL_MIN_FUSION_SCORE` | `0.0` | Relevance floor on the rank_fusion blend value (range [0, 2/60] ≈ [0, 0.033]). Default 0.0 (gate disabled). Values above the blend maximum are rejected at startup. |\n| `RETRIEVAL_RERANK_TOP_N` | `10` | Rerank top N (slug-cut after CE) |\n| `RETRIEVAL_USE_LEAN_WIRE_SCHEMA` | `false` | Use lean response schema |\n| `RETRIEVAL_STYLE_SCORE_THRESHOLD` | `50.0` | Min analysis score for style recommendation |\n| `REASONING_ENABLED` | `true` | Server-side reasoning MCP integration (see [Structured Reasoning](#structured-reasoning-shannonthinking--code-reasoning)) |\n| `REASONING_SPAWN_TIMEOUT_SECONDS` | `10` | Subprocess spawn timeout per reasoning tool |\n| `REASONING_STEP_TIMEOUT_SECONDS` | `20` | Per-thought tool-call timeout |\n| `REASONING_MAX_TOTAL_STEPS` | `8` | Hard cap on reasoning steps per phase |\n| `REASONING_QUIET_STDERR` | `true` | Silence reasoning-subprocess stderr (ASCII progress boxes, `[info]` banners); set `false` to debug spawn failures |\n| `REASONING_FAIL_FAST` | `true` | Fail server startup when a reasoning tool is unreachable |\n| `REASONING_SHANNONTHINKING_CMD` | *(embedded)* | JSON list command for shannonthinking (e.g. `[\"npx\",\"-y\",\"server-shannon-thinking@latest\"]`) |\n| `REASONING_CODE_REASONING_CMD` | *(embedded)* | JSON list command for code-reasoning |\n| `RETRIEVAL_ANALYSIS_BLEND_WEIGHT` | `0.7` | Weight on analysis score in blend |\n| `RETRIEVAL_FUSION_BLEND_WEIGHT` | `0.3` | Weight on fusion score in blend |\n| `RETRIEVAL_WEIGHT_SMOOTHING_ALPHA` | `0.7` | Weight smoothing alpha |\n| `RETRIEVAL_VERBOSE_TIMING` | `false` | Log phase timings at INFO level |\n| `RETRIEVAL_MAX_TRIES` | `3` | Max design loop attempts |\n| `RETRIEVAL_MIN_QUALITY_SCORE` | `50.0` | Early-stop quality threshold |\n| `RERANKER_BASE_URL` | *(default reranker URL)* | TEI reranker endpoint (host:port); model is fixed to `gte-reranker-modernbert-base` |\n| `RERANKER_TIMEOUT` | `30.0` | Reranker timeout (seconds) |\n| `RERANKER_MAX_BATCH_SIZE` | `48` | Max texts per TEI /rerank request; must be ≤ min(MAX_CLIENT_BATCH_SIZE, MAX_CONCURRENT_REQUESTS) of the reranker sidecar. HybridPatternRetriever chunks large pools automatically. |\n| `PATTERN_DIRECTORY` | `~/.config/architecture-pattern-mcp/pattern` | Pattern files directory |\n| `VALIDATION_MAX_RETRIES` | `2` | Max self-healing retry attempts |\n| `VALIDATION_RETRY_ON_FAIL` | `true` | Retry on validation failure |\n\n---\n\n## Structured Reasoning (shannonthinking / code-reasoning)\n\nBefore each LLM phase call (ANALYZE / GENERATE / EVALUATE / RETRY), the server\noptionally runs a bounded **ThoughtGenerator loop**: it authors each reasoning\nstep with the generator's own LLM (LlamaIndex LiteLLM; one completion per\nstep) and submits it to the\n[shannonthinking](https://github.com/olaservo/shannon-thinking) and/or\n[code-reasoning](https://github.com/mettamatt/code-reasoning) MCP servers —\nstructured thinking scratchpads that validate, number, and record each step.\nThe resulting trace is injected into the phase prompt as a\n`<reasoning_context>` block. Contract: **each thought = 1 LLM completion + 1\nMCP tool call**, capped by `REASONING_MAX_TOTAL_STEPS` (default 8).\n\nKey properties:\n\n- **Embedded in Docker** — the `build-mcps` stage bakes both npm packages\n  (`server-shannon-thinking@0.1.1`, `@mettamatt/code-reasoning@0.8.1`) into\n  the image at `/usr/local/lib/node_modules/...`; the runtime invokes them\n  directly via `node` (no network, no npx).\n- **Auto-fallback to npx** outside Docker — when the embedded entry points\n  are missing, the client falls back to `npx -y <pkg>` (first call downloads).\n- **Process-per-call isolation** — each tool call runs in a fresh subprocess\n  (`keep_alive=False`); nothing persists between calls.\n- **Silent per-call degradation** — any spawn/timeout/tool failure logs a\n  WARNING and the phase proceeds with a degraded in-prompt thinking scaffold\n  (decompose → classify → calibrate → resolve → verify); it never raises.\n- **Loud startup** — the lifespan health-check probes both tools and logs an\n  ERROR (with resolution hints) if one is unreachable; set\n  `REASONING_FAIL_FAST=true` to make startup fail instead.\n- **Trace caching** — ANALYZE and GENERATE traces are computed once per\n  design request and reused across design-loop attempts.\n\n### Opting out / tuning\n\n```bash\nexport REASONING_ENABLED=false          # disable entirely\nexport REASONING_FAIL_FAST=true         # refuse to start with broken MCPs\n```\n\nLocal (non-Docker) development needs Node.js; either install the packages\nglobally (`npm install -g server-shannon-thinking @mettamatt/code-reasoning`)\nor let the npx fallback download them on first use.\n\nLatency note: expect roughly +1–6 s per reasoning step. Worst case adds a\ncouple of minutes per design run; the trace cache keeps typical overhead\nwell below that.\n\nSet `LOGGING_LEVEL=DEBUG` to capture the authored `thought` and tool response\nfor every per-step reasoning call. Docker/systemd stacks default to INFO;\nexport `LOGGING_LEVEL=DEBUG` before `make docker-up`.\n| `ARCHITECTURE_PATTERN_JOBS_DB` | `~/.config/architecture-pattern-mcp/jobs.db` | SQLite path for async job trio. Override for test isolation |\n| `TASKS_HEARTBEAT_ENABLED` | `true` | Emit progress notifications during long tool calls |\n| `TASKS_HEARTBEAT_INTERVAL_SECONDS` | `30` | Heartbeat interval in seconds (keep below client idle timeout) |\n| `TRANSPORT` | `streamable-http` | Transport mode: `stdio`, `streamable-http` |\n| `HOST` | `0.0.0.0` | HTTP bind host |\n| `PORT` | `8050` | HTTP bind port |\n| `LOGGING_LEVEL` | `INFO` | Logging level |\n| `LOGGING_FORMAT` | `json` | Logging format: `json`, `text` |\n| `CONFIG_PATH` | `~/.config/architecture-pattern-mcp/config.json` | Config file path |\n\n### CLI flags\n\n| Flag | Description |\n|---|---|\n| `--transport {stdio,streamable-http}` | Override transport mode |\n| `--host` | Override HTTP bind host (default: 0.0.0.0) |\n| `--port` | Override HTTP port (default: 8050) |\n| `--config-path` | Path to config file |\n| `--health` | Run health check and exit |\n\n---\n\n## Extending with Custom Patterns\n\nPattern files are loaded from `~/.config/architecture-pattern-mcp/pattern/` (configurable via `PATTERN_DIRECTORY`). Drop a JSON file alongside the 40 built-in patterns.\n\n**Minimal pattern structure:**\n\n```json\n{\n  \"category\": \"structural\",\n  \"name\": \"my-custom-pattern\",\n  \"context\": \"Describe when this pattern applies.\",\n  \"benefits\": [\"Benefit 1\", \"Benefit 2\"],\n  \"tradeoffs\": [\"Tradeoff 1\"],\n  \"quality_attributes\": {\n    \"scalability\": 7,\n    \"maintainability\": 8,\n    \"reliability\": 7,\n    \"security\": 6,\n    \"performance\": 7,\n    \"simplicity\": 5\n  }\n}\n```\n\nRequired fields: `category`, `name`, `context`, `benefits`, `tradeoffs`, `quality_attributes`.\n\nValid `category` values: `messaging`, `structural`, `cloud`, `data`, `ai_cognitive`, `specialized`, `api_gateway`, `coordination`, `dataflow`, `presentation`.\n\nFull JSON Schema with all enums: `docs/pattern-schema.json`\n\n---\n\n## Long-running tools & timeouts\n\n`design_architecture` (and to a lesser extent `analyze_architecture`, `generate_architecture`, `evaluate_architecture`) run multi-stage LLM pipelines that **can take 5–10 minutes per call**. This is inherent to the workload, not a bug: the generator LLM must process a large input payload — the selected pattern definitions from the 36-pattern catalog, your requirements, and the full output of every previous stage — and produce a large, strictly structured JSON document (components, relationships, API contracts, data models, event contracts, quality scores) one token at a time. The `design_architecture` pipeline repeats generate → evaluate up to three times, so a single call can comprise 9+ LLM round trips.\n\n### The timeout problem\n\nMCP clients (AI coding agents, MCP SDKs) sit between the server and the LLM. Many of them implement a **client-side idle timeout**: if no data is received on the HTTP connection for some period (typically 30–120 seconds), the client aborts the request. The server is still working — the LLM is still generating — but the client closes the connection and reports a timeout error to the agent.\n\nThis is a client-side behaviour, not a server-side one. The server processes the full request correctly; the client simply gives up before the response arrives.\n\n**Affected clients (hardcoded short timeouts):**\n\n| Client | Timeout | Notes |\n|---|---|---|\n| Claude Desktop (TS-SDK) | 60 s | Hardcoded; does not reset on progress notifications |\n| Cursor (TS-SDK) | 60 s | Same as Claude Desktop |\n| Other TS-SDK based agents | varies | Most cap at 60–120 s |\n\nThese clients cannot be reconfigured to accept longer timeouts — the timeout is baked into the SDK.\n\n**Clients covered by the heartbeat defence:**\n\n| Client | Timeout | Defence |\n|---|---|---|\n| Claude Code | ~300 s | Heartbeat every 30 s resets idle timer |\n| OpenCode | ~300 s | Heartbeat every 30 s resets idle timer |\n| Codex CLI | ~300 s | Heartbeat every 30 s resets idle timer |\n| Other HTTP-transport agents | varies | Most reset on any received data |\n\nWorks for these because their idle timers are reset by any incoming data — the heartbeat `progress` notifications sent from a parallel async task on the server are received by the client, resetting its clock.\n\n### The heartbeat defence (applied by default)\n\nEvery long-running tool emits `progress` notifications from a parallel coroutine every 30 seconds (configurable via `TASKS_HEARTBEAT_INTERVAL_SECONDS`). As long as the client resets its idle timer on any received data, the request stays alive for the full duration of the pipeline.\n\n> **TS-SDK clients (Claude Desktop, Cursor, etc.) do not reset their timeout on progress notifications.**\n\n### The async job trio (for timeout-constrained clients)\n\nFor full control and compatibility with timeout-limited clients, three tools provide a durable job handle:\n\n```\nsubmit_architecture_design_job(requirements, domain, override_style)  → job_id\nget_architecture_design_status(job_id)                                  → {status, result, error}\ncancel_architecture_design(job_id)                                      → {cancelled, status}\n```\n\n`submit_architecture_design_job` returns a `job_id` in milliseconds. The pipeline runs in a background task. Poll `get_architecture_design_status(job_id)` every 10–30 seconds. When status is `completed`, the full design is in the `result` field. Cancellation is best-effort — the job exits at the next pipeline stage boundary.\n\n**This is the only fix that works for TS-SDK clients (Claude Desktop, Cursor).**\n\nThe job store is SQLite at `~/.config/architecture-pattern-mcp/jobs.db` (configurable via `ARCHITECTURE_PATTERN_JOBS_DB`).\n\n### Bypassing client timeouts entirely: `make client`\n\nThe example client in `examples/architecture_client.py` is a **direct Python HTTP client** — it is not an MCP agent. It calls the server over HTTP without any MCP SDK, and therefore has **no client-side idle timeout**. It makes a single blocking request and waits for the full response, regardless of how long it takes.\n\n```bash\n# Start the server (from project root)\ndocker compose -f docker/docker-compose.yml up --build\n\n# In another terminal, run the example client\nmake client\n```\n\n`make client` is a development/demo tool. It demonstrates that the server **correctly completes** long requests — the timeout issue is purely a client-side problem. For production use with MCP agents, covers the majority of clients; async job trio is the universal fallback.\n\n## Troubleshooting\n\n### Server starts but tools are not visible\n\n1. Check the agent's MCP connection: Claude Code `/mcp`, OpenCode `opencode mcp list`, Codex `codex mcp list`\n2. Verify the server process started: compose logs should show `MCPArchitectServer initialized`\n3. Confirm the TEI embedder is healthy: `curl http://127.0.0.1:8080/health` inside the container\n\n### \"Connection refused\" or timeout errors\n\nThe server waits for the TEI embedder to become healthy:\n\n```bash\ndocker compose -f docker/docker-compose.yml logs pattern-tei\n```\n\n### LLM provider errors (502 / 401)\n\n- Confirm `GENERATOR_API_KEY` is set and not expired\n- Verify `GENERATOR_BASE_URL` matches your provider's endpoint\n- If using a proxy, check reachability from inside the container\n\n### Pattern JSON files not loading\n\n- Files must have `.json` extension\n- Required fields: `category`, `name`, `context`, `benefits`, `tradeoffs`, `quality_attributes`\n- Validate against `docs/pattern-schema.json`\n\n---\n\n## Building & Development\n\n**Common make targets:**\n\n| Target | Description |\n|---|---|\n| `make install` | Install package in editable mode with dev dependencies |\n| `make lint` | Run ruff linting |\n| `make lint-fix` | Auto-fix lint issues and format |\n| `make typecheck` | Run pyright type checking |\n| `make unit-tests` | Run unit tests with uv (tests/unit/) |\n| `make client` | Run the example MCP client demo (requires server running) |\n| `make docker-build` | Build the MCP server Docker image |\n| `make docker-build-all` | Build MCP server + TEI embedder images |\n| `make docker-publish` | Push image to Docker Hub + GHCR (version + latest) |\n| `make docker-up` | Build and start all services |\n| `make docker-down` | Stop all services |\n**Development workflow:**\n\n```bash\nmake install                      # First-time setup\nmake lint typecheck              # Before pushing\nmake docker-build-all             # Build both images (first time and after code changes)\nmake docker-up   # Start services\nmake docker-logs-follow          # Watch logs\nmake docker-down                 # Stop\n```\n\n---\n\n## Publishing\n\nAll three images are published to two registries simultaneously:\n\n| Image | Docker Hub | GHCR | Tags |\n|---|---|---|---|\n| MCP server | `olkowa/architecture-pattern-mcp` | `ghcr.io/olk/architecture-pattern-mcp` | `$(DOCKER_TAG)`, `latest` |\n| TEI embedder | `olkowa/pattern-tei-embed` | `ghcr.io/olk/pattern-tei-embed` | `$(DOCKER_TAG)`, `latest` |\n| TEI reranker | `olkowa/pattern-tei-rerank` | `ghcr.io/olk/pattern-tei-rerank` | `$(DOCKER_TAG)`, `latest` |\n\nAll three images share the same `$(DOCKER_TAG)` (the version from `pyproject.toml`), so `tei:1.0.3` always ships with `mcp:1.0.3`. Blob deduplication keeps re-tagging unchanged TEI images cheap.\n\n> **Bandwidth note:** the TEI embedder image is ~5 GB (ONNX fp32 weights baked in). First push to each registry is ~5 GB upload. Subsequent pushes are incremental — only changed layers are transferred.\n\n### Prerequisites\n\n**Docker Hub** — already authenticated locally (`docker login`).\n\n**GitHub Container Registry** — requires a [classic PAT](https://github.com/settings/tokens/new?scopes=write:packages) with `write:packages` scope. 2FA is not an issue — PATs bypass it. After login the token is discarded; the credential persists in `~/.docker/config.json` until you log out.\n\n### Publish (one-time setup + per-session)\n\n```bash\n# 1. Login to GHCR (interactive — paste token at the password prompt)\ndocker login ghcr.io -u olk\n\n# 2. Build and push all three images (MCP + TEI embedder + TEI reranker)\n#    The umbrella target builds the MCP image, tags it, pushes it, creates the git tag,\n#    then builds and pushes each TEI image in sequence.\nmake docker-publish-all\n\n# 3. Logout from GHCR immediately after publishing\n#    (removes the ghcr.io credential from ~/.docker/config.json)\ndocker logout ghcr.io\n```\n\nOn subsequent publishes repeat steps 1–3. If your PAT has expired, generate a new one at the link above.\n\n### First push — set packages public (GHCR only)\n\nGHCR packages default to **private**. After the first `make docker-publish-all`, flip all three packages to public:\n\n| Package | Settings URL |\n|---|---|\n| MCP server | `https://github.com/users/olk/packages/container/architecture-pattern-mcp/settings` |\n| TEI embedder | `https://github.com/users/olk/packages/container/pattern-tei-embed/settings` |\n| TEI reranker | `https://github.com/users/olk/packages/container/pattern-tei-rerank/settings` |\n\nSet each to **Public** and save.\n\n### Partial failure recovery\n\nIf the push fails mid-way (e.g., GHCR auth was not configured), Docker Hub layers are already uploaded. After fixing auth, re-running `make docker-publish-all` is safe — each registry reports a cache hit for already-uploaded layers and completes the remaining push. For targeted retries, individual images can be pushed with `make docker-publish-tei` or `make docker-publish-tei-rerank`.\n\n---\n\n## systemd Service (Linux)\n\nThe server can run as a systemd service on any systemd-based Linux host. It\nstarts the Docker Compose stack automatically at boot.\n\n### File layout\n\nThe `systemd/` directory contains three files:\n\n| File | Purpose |\n|---|---|\n| `systemd/architecture-pattern-mcp.service` | The systemd unit |\n| `systemd/docker-compose.yml` | Production compose variant (no `build:`, absolute paths) |\n| `systemd/README.md` | Full runbook with install, verify, and troubleshooting |\n\nThe production compose file is a deployment variant of `docker/docker-compose.yml`:\nit has no `build:` sections (images must be pre-built), uses absolute paths, and\nlives under `/etc/architecture-pattern-mcp/` on the host. The systemd-managed\nproject uses the distinct name `apmcp-systemd` so it can coexist with the dev\ncompose if needed.\n\n> **TEI sidecars are NOT defined in this stack** — the `pattern-tei-embed` embedder\n> and `pattern-tei-rerank` reranker containers live in the shared\n> [`pattern-tei-infra`](https://github.com/olk/pattern-tei-infra) stack.  This\n> stack owns the `pattern-tei-shared` Docker network and exposes the sidecars at\n> `http://pattern-tei-embed:8080/v1` (embedder) and `http://pattern-tei-rerank:8080`\n> (reranker).  The systemd MCP stack joins that network and reaches them by\n> those DNS names.\n>\n> **Prerequisite — one-time TEI infra setup:**\n>\n> ```bash\n> # Clone the infra stack (if not already on the host)\n> git clone https://github.com/olk/pattern-tei-infra.git ~/pattern-tei-infra\n>\n> # Install and enable the pattern-tei-infra systemd unit\n> sudo install -m 644 ~/pattern-tei-infra/pattern-tei-infra.service \\\n>                 /etc/systemd/system/\n> sudo systemctl daemon-reload\n> sudo systemctl enable --now pattern-tei-infra.service\n> # Wait ~2 min for the TEI sidecars to become healthy\n> ```\n>\n> **Start `architecture-pattern-mcp.service` only AFTER** `pattern-tei-infra.service`\n> is `active (running)`. See\n> [`pattern-tei-infra/README.md`](https://github.com/olk/pattern-tei-infra/README.md)\n> for full details.\n\n### Prerequisites\n\n- systemd-based Linux host with Docker (`docker compose version`).\n- `<user>` is in the `docker` group.\n- Both images pre-built locally (`make docker-build-all` from the repo).\n- **Shared TEI infra stack installed and enabled** (see tei-infra README).\n\n### Install\n\n```bash\n# 0. Install + enable shared TEI infra (once)\nsudo install -m 644 {HOME}/pattern-tei-infra/pattern-tei-infra.service /etc/systemd/system/\nsudo systemctl daemon-reload\nsudo systemctl enable --now pattern-tei-infra.service\n\n# 1. Build images (once)\nmake docker-build-all\n\n# 2. Deploy /etc/architecture-pattern-mcp/\nsudo install -d /etc/architecture-pattern-mcp/config\nsudo install -m 644 systemd/docker-compose.yml /etc/architecture-pattern-mcp/\nsudo install -m 644 ~/.config/architecture-pattern-mcp/config.json /etc/architecture-pattern-mcp/config/\n\n# 3. Create the .env file (root:docker 640) and edit it.\n#     640 root:docker — not 600 root:root — so the systemd service\n#     running as User=graemer (a member of the `docker` group) can read this\n#     file when docker compose auto-loads it.  The `docker` group is\n#     effectively privileged; this is the standard trade-off for non-root\n#     systemd services that manage Docker containers.\nsudo install -o root -g docker -m 640 /dev/null /etc/architecture-pattern-mcp/.env\nsudo $EDITOR /etc/architecture-pattern-mcp/.env\n# Contents:\n#   MINIMAXAI_API_KEY=sk-...\n#   COMPOSE_PROJECT_NAME=apmcp-systemd\n#   MCP_HOST_PORT=8050          # change to avoid port conflicts with other MCP servers\n\n# 4. Install and enable the service.\nsudo install -m 644 systemd/architecture-pattern-mcp.service /etc/systemd/system/\nsudo systemctl daemon-reload\nsudo systemctl enable --now architecture-pattern-mcp.service\n```\n\n### Verify\n\n`systemctl status` shows `active (exited)` within seconds, but the containers\ntake **up to ~2 minutes** to become healthy (TEI embedder `start_period: 120s`).\nThe unit does not wait for healthchecks.\n\n```bash\nsystemctl status architecture-pattern-mcp\njournalctl -u architecture-pattern-mcp -n 50\ndocker compose -p apmcp-systemd -f /etc/architecture-pattern-mcp/docker-compose.yml ps\ncurl -fsS http://localhost:${MCP_HOST_PORT:-8050}/health\n```\n\n### Day-to-day\n\n```bash\nsudo systemctl start|stop|restart|reload architecture-pattern-mcp\njournalctl -u architecture-pattern-mcp -n 200 -f\ndocker compose -p apmcp-systemd -f /etc/architecture-pattern-mcp/docker-compose.yml logs -f\n```\n\n### Updating the stack\n\n```bash\nmake docker-build-all                     # rebuild both images\nsudo systemctl reload architecture-pattern-mcp   # recreate containers\n```\n\n### Uninstall\n\n```bash\nsudo systemctl disable --now architecture-pattern-mcp.service\nsudo rm /etc/systemd/system/architecture-pattern-mcp.service\nsudo systemctl daemon-reload\nsudo rm -rf /etc/architecture-pattern-mcp\n```\n\nFor full troubleshooting, networking details, and the coexistence guide, see\n`systemd/README.md`.\n\n---\n\n## License\n\nMIT License. See [LICENSE](LICENSE).\n",
  "bytes": 41156,
  "sha": "3f7ccecbc44db19d965d0c83679811b6ad25b570d8982b071cacae8d98e7145e",
  "repo_slug": "olk/architecture-pattern-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_olk_architecture_pattern_mcp_e8d91876/readme"
}