| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(mcp): MCP server v1 — mount on /mcp, per-agent voice binding, stdio shim (Wave 2.2) (#368) * feat(mcp): MCP server v1 — mount on /mcp, per-agent voice binding, stdio shim (Wave 2.2) The FastMCP server (previously dead code, never mounted) is now mounted on the main FastAPI app at /mcp via Streamable HTTP, with its session manager composed into the app lifespan through an AsyncExitStack (best-effort: a missing mcp package or OMNIVOICE_MCP_DISABLE=1 never breaks startup). streamable_http_path set to '/' so the sub-mount lands at /mcp, not /mcp/mcp. Adds the 'mcp' dependency (1.27.x). Per-agent voice binding (Spec 2 headline): each MCP client sends an X-OmniVoice-Client-Id header; generate_speech resolves the voice as explicit arg > the client's binding > global default > app default. New mcp_client_bindings table (alembic 0004 + _BASE_SCHEMA, additive/idempotent), services/mcp_bindings.py (CRUD + resolve_voice + best-effort last_seen), and a loopback-gated REST router (/api/mcp/bindings) the Settings panel drives. New transcribe tool (base64 audio in, 200 MB cap). Stdio shim (backend/mcp_shim, httpx-only, ported from voicebox MIT) proxies stdio clients to the mounted endpoint and forwards OMNIVOICE_CLIENT_ID as the binding header. Settings → Sharing gains an MCP bindings panel. Docs: docs/mcp.md (both connection modes + binding REST) and docs/mcp.json updated to the shim form. Tests: bindings service + resolution precedence + migration up/down (pure, run locally); REST CRUD + mount-not-404 + disable-flag (main-importing, validated in CI). MCP build + mount + initialize handshake verified out-of-band (no torch). Spec: docs/competitive-analysis.md Spec 2 / parity program Wave 2.2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(mcp): assert /mcp mount via app.routes, not a lifespan client The two main-importing mount tests ran the app lifespan, which now starts the FastMCP session manager and binds asyncio queues to the test loop — contaminating later lifespan-running tests ('bound to a different event loop'). The mount happens at import time, so inspecting app.routes for the /mcp Mount is the correct loop-free assertion. Same fix shape as the Wave 0.2 consent tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(mcp): stop reload-main poisoning across the MCP test files Root cause of the CI failure: the bindings REST fixture set OMNIVOICE_MCP_DISABLE=1 and reloaded main but never restored it, so a later 'from main import app' in test_mcp_mount saw /mcp un-mounted ({'/audio','/voice_audio'}). Reloading main mutates the shared module for every subsequent test. - REST fixture: drop the disable flag (the mount is harmless without a lifespan), yield the client, and restore main (+ core.config/db) to the default data dir in teardown so the global module is clean again. - test_main_mounts_mcp_route: reload main with the disable flag cleared so the assertion is independent of any earlier reload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 3 个月前 | |
fix(integrations): working MCP, Codex, API and Docker setups; honest catalog badges (#2289) * fix(mcp): serve /mcp without redirect, follow OMNIVOICE_PORT, real integration setups - /mcp and /mcp/ both reach the Streamable HTTP transport for every method, ahead of the SPA StaticFiles mount (POST /mcp was 405 on Docker/source builds, 307 without a built SPA). Regression test drives initialize -> tools/list -> DELETE on the real main.app with a dist dir and follow_redirects=False; the client-setup test no longer follows redirects. - MCP tool callbacks resolve the backend URL from OMNIVOICE_BIND_HOST + OMNIVOICE_PORT (OMNIVOICE_API_URL still overrides) instead of a hard-coded :3900; speech_client and the dev fallback redirect follow their port env too, with a class guard against literal :39xx URLs in backend code. - Integrations: setup registry keyed by slug drives the Works with VoiceStudio badge and capability chips; adds Codex CLI config.toml, generic MCP (HTTP + stdio shim), VoiceStudio API (curl + OpenAI SDK) and Docker/GHCR setups. Removes the fake category chips from the shared catalog config; fixes the duplicate Details headings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: pin capture path in speech envelope test; changelog refs (#2289) A prior lifespan-running test persists a sherpa dictation.model_id pref via the startup performance profile, routing the envelope test to the sherpa handler. Pin the capture path it asserts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(integrations): export <base>/mcp/ for every MCP client The trailing-slash URL works on backends without the bare-/mcp fix too, so Claude Code, Cursor, Codex and the generic MCP card all export it. Docs lead with /mcp/ and note bare /mcp works on current backends. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): in-process tool callbacks; remote-safe stdio and API snippets Review findings on #2289: - Greptile P1: mounted MCP tools now call the backend app in-process via httpx.ASGITransport as a loopback caller, so a concrete LAN OMNIVOICE_BIND_HOST behind an API key / share PIN no longer 401s every tool. Standalone runs keep the HTTP backend_self_url() path. - Greptile P1: the stdio shim accepts OMNIVOICE_URL (https + path prefix) and forwards OMNIVOICE_API_KEY as a Bearer token; the MCP card exports the full base URL instead of host/port. - Greptile P1: remote API snippets read the key from $OMNIVOICE_API_KEY (curl Bearer header, Python os.environ) without exporting credentials. - CodeQL (Bandit B104): wildcard detection uses ipaddress.is_unspecified instead of a 0.0.0.0 literal. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(integrations): API keys only over https or loopback; reference them by env CodeRabbit findings on #2289: - Standalone MCP server and the stdio shim send OMNIVOICE_API_KEY as a Bearer token only to https or loopback targets (same rule as backend.speech_client); the shim refuses to start otherwise. - Remote https exports reference the key from the user's environment in each client's own syntax (Claude Code ${VAR}, Cursor ${env:VAR}, Codex bearer_token_env_var, curl/Python $OMNIVOICE_API_KEY); plain-http remotes export no key. - test_backend_self_url imports app modules inside the tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(integrations): credential guidance via translated hints; HTTP card auth line CodeRabbit findings on #2289: - The generic Streamable HTTP card lists the env-backed Authorization header for remote https backends. - English comments inside copyable snippets (API credentials, Docker GPU) move to translated hints (apiBearerHint, apiInsecureHint, dockerGpuHint, all 21 locales); a test forbids natural-language comments in snippets. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(integrations): PowerShell docker run; scheme-aware Agents SDK key CodeRabbit findings on #2289: - Docker/GHCR pages add a Windows PowerShell docker run (backtick continuations, CSPRNG key that works on PowerShell 5.1 and 7), so the default setup works on every platform. - The OpenAI Agents snippet only reads OMNIVOICE_API_KEY for loopback or https backends; a remote plain-http backend gets a placeholder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(integrations): Agents SDK key only for remote https backends Greptile/CodeRabbit findings on #2289: the OpenAI Agents snippet now follows the shared remoteAuth policy exactly. It requires os.environ["OMNIVOICE_API_KEY"] for remote https backends, uses a placeholder on loopback (no key over plain http, even locally), and for a remote plain-http backend shows the translated 'use https' hint instead of implying the key will be sent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: drop the speech-platform capture pin now that #2294 isolates settings Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(integrations): remote https Python snippets tolerate an unkeyed backend Greptile finding on #2289: os.environ[...] raised KeyError on a remote https backend that runs without an API key. Read the key with a placeholder fallback (Agents SDK and API Python snippets); still loopback- and plain-http-safe via remoteAuth. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> | 1 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 3 个月前 | ||
| 1 天前 |