feat(mcp): FastMCP server exposing read-only KB tools (stdio)

Implements F-14: thin FastMCP wrapper over existing codex domain modules.
Seven read-only tools: search, ask, wiki_read, wiki_list, discover_leads,
provenance_verify, synthesis_browse. All optional-feature tools degrade
gracefully to {"error": "feature not available"} instead of crashing.
Adds mcp[cli]>=1.0 dependency and codex-mcp console_script entry-point.
28 new tests across test_server, test_search, test_readonly, test_graceful,
test_http_auth — all green; 0 regressions in existing 172 tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-06-14 08:23:22 +02:00
parent 59763105a5
commit b803811701
10 changed files with 1484 additions and 0 deletions

View File

@@ -222,6 +222,93 @@ class Settings(BaseSettings):
),
)
# ------------------------------------------------------------------
# F-13 Synthesis / Lead-Engine
# ------------------------------------------------------------------
leads_dir: str = Field(
default="leads/",
description=(
"Directory where synthesis leads are written. "
"Grounded leads land in <leads_dir>/grounded/, conjectures in "
"<leads_dir>/conjectures/. INVARIANT: conjectures NEVER touch wiki/."
),
)
synthesis_llm_model: str = Field(
default="qwen2.5:7b",
description=(
"Ollama model name used for synthesis (connection / gap / improvement / "
"conjecture). Defaults to the qwen-light profile on the Jetson."
),
)
synthesis_llm_url: str | None = Field(
default=None,
description=(
"Ollama base URL for synthesis. When None, falls back to OLLAMA_BASE_URL."
),
)
synthesis_top_k: int = Field(
default=20,
gt=0,
description=(
"Number of top chunks retrieved per synthesis probe. Higher values "
"improve recall at the cost of a larger LLM prompt."
),
)
synthesis_min_grounded_ratio: float = Field(
default=0.5,
ge=0.0,
le=1.0,
description=(
"Minimum fraction of claims that must be grounded for a grounded lead "
"(connection / gap / improvement) to be emitted. Leads below this "
"threshold are dropped rather than written to leads/grounded/."
),
)
synthesis_gap_min_coverage: int = Field(
default=2,
gt=0,
description=(
"A topic is considered a 'gap' candidate when it is covered by fewer "
"than this many distinct bibkeys in the retrieved top-K."
),
)
# ------------------------------------------------------------------
# F-14 MCP Server
# ------------------------------------------------------------------
mcp_transport: str = Field(
default="stdio",
description=(
"Transport protocol for the MCP server. "
"Allowed values: 'stdio' (default) or 'http'. "
"HTTP transport requires MCP_AUTH_TOKEN to be set."
),
)
mcp_host: str = Field(
default="127.0.0.1",
description="Bind host for HTTP transport (ignored for stdio).",
)
mcp_port: int = Field(
default=8765,
gt=0,
description="Bind port for HTTP transport (ignored for stdio).",
)
mcp_auth_token: str | None = Field(
default=None,
description=(
"Bearer token required when mcp_transport='http'. "
"If HTTP transport is requested but this is None, the server refuses to start."
),
)
@lru_cache(maxsize=1)
def get_settings() -> Settings: