cade-cli 0.15.0__tar.gz → 0.15.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cade_cli-0.15.0 → cade_cli-0.15.2}/PKG-INFO +2 -2
- {cade_cli-0.15.0 → cade_cli-0.15.2}/README.md +1 -1
- {cade_cli-0.15.0 → cade_cli-0.15.2}/pyproject.toml +1 -1
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/server.py +26 -2
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/app.py +81 -2
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/auth.py +10 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/mcp.py +124 -43
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/manager/__init__.py +5 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/manager/config.py +34 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/.gitignore +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/LICENSE +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/__main__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/_metadata.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/config.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/errors.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/resources.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/_read_cache.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/agents.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/context.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/filesystem.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/git.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/notifications.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/questions.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/results.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/schemas.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/search.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/shell.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/snippets.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/tasks.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/web_fetch.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/tools/web_search.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/utils.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/arcade_client.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/cache.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/extract.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/markdown.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/preapproved.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/prompts.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/scrape.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/search/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/search/arcade.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/search/base.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cade_mcp_local/web/search/http.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ai/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ai/prompts.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/auth.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/account.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/agents.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/chat.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/cron.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/hooks.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/mem.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/serve.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/tasks.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/thread.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/commands/tools.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/cli/onboarding.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/agent.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/comms.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/config.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/constants.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/errors.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/frozen.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/git.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/inbox.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/logging.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/memory.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/model_specs.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/names.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/paths.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/runtime.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/secrets.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/spawn.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/core/types.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/execution/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/execution/context_window.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/execution/mcp_schema_index.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/execution/orchestrator.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/execution/parallel.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/execution/tool_result_store.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/execution/tool_schema_cache.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/config.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/engine.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/executors/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/executors/callback.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/executors/command.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/executors/http.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/executors/prompt.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/matchers.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/registry.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/hooks/types.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/providers/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/providers/anthropic.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/providers/base.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/providers/ollama.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/providers/openai.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/adapters/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/adapters/base.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/adapters/desktop.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/adapters/registry.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/adapters/stub.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/adapters/telegram.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/allowlist.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/chunker.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/commands.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/config.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/cron.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/daemon.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/elicitation.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/format.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/install.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/lock.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/observability.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/owner.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/progress_sink.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/router.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/secrets.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/services.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/sinks.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/serve/worker.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/storage/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/storage/threads.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tasks/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tasks/lock.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tasks/scheduler.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tasks/store.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tasks/types.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/templates/login_failed.html +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/templates/login_success.html +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/templates/serve/serve.toml.example +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/templates/styles.css +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/manager/base.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/manager/composite.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/manager/mcp.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/search/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/search/discovered.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/search/scoring.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/tools/search/service.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ui/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ui/completion.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ui/display.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ui/elicitation.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ui/input.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ui/session.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/ui/slash.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/voice/__init__.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/voice/audio.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/voice/cleanup.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/voice/session.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/voice/stt.py +0 -0
- {cade_cli-0.15.0 → cade_cli-0.15.2}/src/cadecoder/voice/tts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cade-cli
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.2
|
|
4
4
|
Summary: Cade - The CLI Agent from Arcade.dev
|
|
5
5
|
Project-URL: Homepage, https://arcade.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.arcade.dev
|
|
@@ -123,7 +123,7 @@ The install script detects your OS/arch, downloads the matching prebuilt binary
|
|
|
123
123
|
`~/.local/bin` — no GitHub token required. Pin a version or install location:
|
|
124
124
|
|
|
125
125
|
```bash
|
|
126
|
-
CADE_VERSION=0.15.
|
|
126
|
+
CADE_VERSION=0.15.2 CADE_INSTALL_DIR=/usr/local/bin \
|
|
127
127
|
sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
|
|
128
128
|
```
|
|
129
129
|
|
|
@@ -35,7 +35,7 @@ The install script detects your OS/arch, downloads the matching prebuilt binary
|
|
|
35
35
|
`~/.local/bin` — no GitHub token required. Pin a version or install location:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
CADE_VERSION=0.15.
|
|
38
|
+
CADE_VERSION=0.15.2 CADE_INSTALL_DIR=/usr/local/bin \
|
|
39
39
|
sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -74,6 +74,19 @@ external services. For those, use the appropriate remote/cloud tools instead.
|
|
|
74
74
|
"""
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def web_search_backend_configured() -> bool:
|
|
78
|
+
"""True if the WebSearch tool has a usable backend.
|
|
79
|
+
|
|
80
|
+
WebSearch needs either an Arcade key (``ARCADE_API_KEY``) or a generic
|
|
81
|
+
HTTP/SearXNG endpoint (``CADE_WEB_SEARCH_URL``). When neither is set the
|
|
82
|
+
tool is not advertised, so the model never calls a search it can't run.
|
|
83
|
+
"""
|
|
84
|
+
from cade_mcp_local.web.search.arcade import ArcadeSearchBackend
|
|
85
|
+
from cade_mcp_local.web.search.http import HttpBackend
|
|
86
|
+
|
|
87
|
+
return ArcadeSearchBackend.is_configured() or HttpBackend.is_configured()
|
|
88
|
+
|
|
89
|
+
|
|
77
90
|
def create_app() -> MCPApp:
|
|
78
91
|
"""Create and configure the MCP application with all local tools."""
|
|
79
92
|
app = MCPApp(
|
|
@@ -128,9 +141,20 @@ def create_app() -> MCPApp:
|
|
|
128
141
|
# Structured clarification question for the user
|
|
129
142
|
app.add_tool(ask_user_question_tool)
|
|
130
143
|
|
|
131
|
-
# Web tools (
|
|
144
|
+
# Web tools. WebFetch always works (httpx fallback + the agent's own LLM for
|
|
145
|
+
# extraction). WebSearch needs a backend — an Arcade key or a SearXNG/HTTP
|
|
146
|
+
# endpoint — so only advertise it when one is configured; otherwise the model
|
|
147
|
+
# would call it and hit a confusing "no search backend configured" error on
|
|
148
|
+
# first use. Once the user connects Omni, web search comes from the Arcade
|
|
149
|
+
# catalogue (Arcade_SelectTools/UseTool) instead.
|
|
150
|
+
#
|
|
151
|
+
# Note: this gates the shipped (frozen) binary, which serves the local tools
|
|
152
|
+
# via this create_app(). In source/dev mode the local server is run through
|
|
153
|
+
# arcade_mcp_server's directory discovery, which registers every @tool and
|
|
154
|
+
# bypasses this gate — developers typically have a key set anyway.
|
|
132
155
|
app.add_tool(web_fetch_tool)
|
|
133
|
-
|
|
156
|
+
if web_search_backend_configured():
|
|
157
|
+
app.add_tool(web_search_tool)
|
|
134
158
|
|
|
135
159
|
# MCP resources: server instructions + memory sources for the active persona
|
|
136
160
|
register_resources(app, SERVER_INSTRUCTIONS)
|
|
@@ -1,7 +1,75 @@
|
|
|
1
1
|
"""Main CLI application setup and entry point for CadeCoder."""
|
|
2
2
|
|
|
3
|
+
import multiprocessing
|
|
3
4
|
import os
|
|
4
|
-
import
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Defensive: harmless no-op on macOS/Linux, required on Windows if any bundled
|
|
8
|
+
# dependency uses multiprocessing in a frozen build.
|
|
9
|
+
multiprocessing.freeze_support()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _python_style_target(argv: list[str]) -> tuple[str, str, list[str]] | None:
|
|
13
|
+
"""Classify a ``python``-style ``-m``/``-c`` invocation. Pure + testable.
|
|
14
|
+
|
|
15
|
+
Returns ``("module", name, rest)`` or ``("code", code, rest)`` if ``argv``
|
|
16
|
+
looks like ``<exe> -m <importable.module> ...`` or ``<exe> -c <code> ...``;
|
|
17
|
+
otherwise ``None``. A ``-m`` argument only counts when it's an importable
|
|
18
|
+
dotted module path (what a dependency passes to ``python -m``), so a real
|
|
19
|
+
chat message like ``-m "fix the bug"`` is never misclassified.
|
|
20
|
+
"""
|
|
21
|
+
if len(argv) >= 2 and argv[1] == "-c":
|
|
22
|
+
return ("code", argv[2] if len(argv) >= 3 else "", list(argv[3:]))
|
|
23
|
+
if len(argv) >= 3 and argv[1] == "-m":
|
|
24
|
+
module = argv[2]
|
|
25
|
+
if "." in module and not any(c.isspace() for c in module):
|
|
26
|
+
try:
|
|
27
|
+
import importlib.util
|
|
28
|
+
|
|
29
|
+
if importlib.util.find_spec(module) is not None:
|
|
30
|
+
return ("module", module, list(argv[3:]))
|
|
31
|
+
except (ImportError, ValueError):
|
|
32
|
+
pass
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _run_python_style_invocation_if_frozen() -> None:
|
|
37
|
+
"""In frozen builds, emulate ``python -m <module>`` / ``python -c <code>``.
|
|
38
|
+
|
|
39
|
+
Several dependencies shell out to ``sys.executable`` assuming it is a Python
|
|
40
|
+
interpreter. The most damaging is arcade-core's detached PostHog usage
|
|
41
|
+
reporter, which runs ``[sys.executable, "-m", "arcade_core.usage"]`` for
|
|
42
|
+
every usage event. In a PyInstaller binary ``sys.executable`` is THIS cli,
|
|
43
|
+
so ``cade -m arcade_core.usage`` was parsed as single-message mode — it
|
|
44
|
+
started a whole agent, which emitted more usage events, each spawning
|
|
45
|
+
another agent: a fork bomb of hundreds of ``cade`` processes.
|
|
46
|
+
|
|
47
|
+
Intercept the Python-style ``-m``/``-c`` invocations here, before Typer sees
|
|
48
|
+
them, and run them via ``runpy``/``exec`` so the frozen binary behaves like
|
|
49
|
+
the interpreter the dependency expected. This both stops the fork bomb and
|
|
50
|
+
lets the telemetry subprocess do its (lightweight, one-shot) job. No-op for
|
|
51
|
+
normal use, including ``cade -m "a chat message"``.
|
|
52
|
+
"""
|
|
53
|
+
if not getattr(sys, "frozen", False):
|
|
54
|
+
return
|
|
55
|
+
target = _python_style_target(sys.argv)
|
|
56
|
+
if target is None:
|
|
57
|
+
return
|
|
58
|
+
kind, what, rest = target
|
|
59
|
+
if kind == "code":
|
|
60
|
+
sys.argv = ["-c", *rest]
|
|
61
|
+
exec(compile(what, "<string>", "exec"), {"__name__": "__main__"})
|
|
62
|
+
else:
|
|
63
|
+
import runpy
|
|
64
|
+
|
|
65
|
+
sys.argv = [what, *rest]
|
|
66
|
+
runpy.run_module(what, run_name="__main__", alter_sys=True)
|
|
67
|
+
raise SystemExit(0)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_run_python_style_invocation_if_frozen()
|
|
71
|
+
|
|
72
|
+
import warnings # noqa: E402
|
|
5
73
|
|
|
6
74
|
# Set environment variable to disable tokenizers parallelism warning
|
|
7
75
|
# This must be set before any imports that use tokenizers
|
|
@@ -390,8 +458,9 @@ def main_callback(
|
|
|
390
458
|
# when not attached to an interactive terminal (piped/CI).
|
|
391
459
|
from cadecoder.cli.onboarding import needs_onboarding, run_onboarding
|
|
392
460
|
|
|
461
|
+
did_onboard = False
|
|
393
462
|
if needs_onboarding():
|
|
394
|
-
run_onboarding(console)
|
|
463
|
+
did_onboard = run_onboarding(console)
|
|
395
464
|
|
|
396
465
|
# Interaction + runtime-backed commands run as the current agent, auto-creating
|
|
397
466
|
# a `default` agent on first run via build_runtime → ensure_current_agent.
|
|
@@ -399,6 +468,16 @@ def main_callback(
|
|
|
399
468
|
set_runtime(runtime)
|
|
400
469
|
ctx.obj["runtime"] = runtime
|
|
401
470
|
|
|
471
|
+
# Right after first-run setup (runtime now available), offer to connect
|
|
472
|
+
# Arcade Omni so external tools work out of the box. No-op otherwise.
|
|
473
|
+
if did_onboard:
|
|
474
|
+
try:
|
|
475
|
+
from cadecoder.cli.commands.mcp import offer_connect_omni
|
|
476
|
+
|
|
477
|
+
offer_connect_omni(console)
|
|
478
|
+
except Exception: # noqa: BLE001
|
|
479
|
+
pass
|
|
480
|
+
|
|
402
481
|
# Handle single message mode
|
|
403
482
|
if message is not None and ctx.invoked_subcommand is None:
|
|
404
483
|
import sys
|
|
@@ -86,6 +86,16 @@ def login(
|
|
|
86
86
|
|
|
87
87
|
log.info(f"User {whoami.email} logged in via OAuth.")
|
|
88
88
|
|
|
89
|
+
# Offer to connect Arcade Omni so the agent can use external tools
|
|
90
|
+
# (Gmail, Slack, web search, …) without a separate API key. No-op when
|
|
91
|
+
# non-interactive or already authorized.
|
|
92
|
+
try:
|
|
93
|
+
from cadecoder.cli.commands.mcp import offer_connect_omni
|
|
94
|
+
|
|
95
|
+
offer_connect_omni(console)
|
|
96
|
+
except Exception as e: # noqa: BLE001
|
|
97
|
+
log.debug(f"omni connect offer skipped: {e}")
|
|
98
|
+
|
|
89
99
|
except OAuthLoginError as e:
|
|
90
100
|
console.print(f"\n[red]✗ Login failed:[/red] {escape(str(e))}")
|
|
91
101
|
raise typer.Exit(code=1)
|
|
@@ -12,10 +12,12 @@ from rich.console import Console
|
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
from cadecoder.tools.manager import (
|
|
15
|
+
KNOWN_SERVERS,
|
|
15
16
|
MCPAuthType,
|
|
16
17
|
MCPServerConfig,
|
|
17
18
|
MCPServerStore,
|
|
18
19
|
MCPToolManager,
|
|
20
|
+
known_server_config,
|
|
19
21
|
)
|
|
20
22
|
from cadecoder.tools.manager.mcp import MCP_PROTOCOL_VERSION
|
|
21
23
|
|
|
@@ -37,15 +39,29 @@ def mcp_callback(ctx: typer.Context) -> None:
|
|
|
37
39
|
raise typer.Exit(0)
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
def _print_known_servers_hint(configured: set[str]) -> None:
|
|
43
|
+
"""Show built-in known servers the user hasn't connected yet."""
|
|
44
|
+
available = [name for name in KNOWN_SERVERS if name not in configured]
|
|
45
|
+
if not available:
|
|
46
|
+
return
|
|
47
|
+
console.print("\n[dim]Connectable services (OAuth):[/dim]")
|
|
48
|
+
for name in available:
|
|
49
|
+
desc = KNOWN_SERVERS[name].get("description", "")
|
|
50
|
+
console.print(f" [cyan]{name}[/cyan] — {desc}")
|
|
51
|
+
console.print(f" [dim]Connect with [bold]cade mcp authorize {available[0]}[/bold].[/dim]")
|
|
52
|
+
|
|
53
|
+
|
|
40
54
|
@mcp_app.command("list")
|
|
41
55
|
def list_servers() -> None:
|
|
42
56
|
"""List all configured MCP servers."""
|
|
43
57
|
store = MCPServerStore()
|
|
44
58
|
servers = store.list_all()
|
|
59
|
+
configured = {s.name for s in servers}
|
|
45
60
|
|
|
46
61
|
if not servers:
|
|
47
62
|
console.print("[yellow]No MCP servers configured.[/yellow]")
|
|
48
63
|
console.print("[dim]Use 'cade mcp add <name> <url>' to add a server.[/dim]")
|
|
64
|
+
_print_known_servers_hint(configured)
|
|
49
65
|
return
|
|
50
66
|
|
|
51
67
|
table = Table(title="MCP Servers")
|
|
@@ -62,7 +78,9 @@ def list_servers() -> None:
|
|
|
62
78
|
server.last_connected.strftime("%Y-%m-%d %H:%M") if server.last_connected else "-"
|
|
63
79
|
)
|
|
64
80
|
auth_display = server.auth_type.value
|
|
65
|
-
if server.auth_type
|
|
81
|
+
if server.auth_type == MCPAuthType.OAUTH:
|
|
82
|
+
auth_display += " ✓" if server.oauth_tokens else " ✗"
|
|
83
|
+
elif server.auth_type != MCPAuthType.NONE:
|
|
66
84
|
auth_display += " ✓" if server.auth_value else " ✗"
|
|
67
85
|
|
|
68
86
|
table.add_row(
|
|
@@ -75,6 +93,7 @@ def list_servers() -> None:
|
|
|
75
93
|
)
|
|
76
94
|
|
|
77
95
|
console.print(table)
|
|
96
|
+
_print_known_servers_hint(configured)
|
|
78
97
|
|
|
79
98
|
|
|
80
99
|
@mcp_app.command("add")
|
|
@@ -343,40 +362,35 @@ class OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
|
343
362
|
pass
|
|
344
363
|
|
|
345
364
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
client_id:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
typer.Option("--skip-dcr", help="Skip Dynamic Client Registration"),
|
|
360
|
-
] = False,
|
|
361
|
-
) -> None:
|
|
362
|
-
"""Initiate OAuth authorization flow for an MCP server.
|
|
363
|
-
|
|
364
|
-
This command will:
|
|
365
|
-
1. Discover the authorization server from the MCP server
|
|
366
|
-
2. Register as a client (DCR) if no client_id is configured
|
|
367
|
-
3. Open a browser for you to authorize
|
|
368
|
-
4. Capture the callback and exchange the code for tokens
|
|
369
|
-
5. Store the tokens for future use
|
|
365
|
+
def run_mcp_authorization(
|
|
366
|
+
name: str,
|
|
367
|
+
*,
|
|
368
|
+
client_id: str | None = None,
|
|
369
|
+
scope: str | None = None,
|
|
370
|
+
skip_dcr: bool = False,
|
|
371
|
+
) -> bool:
|
|
372
|
+
"""Run the MCP OAuth authorization flow for a server. Returns success.
|
|
373
|
+
|
|
374
|
+
If ``name`` isn't configured yet but is a built-in known server (e.g.
|
|
375
|
+
``omni``), it's added in a disabled state first and only enabled once
|
|
376
|
+
authorization succeeds — so a cancelled/failed flow never leaves a broken
|
|
377
|
+
server that would error during chat.
|
|
370
378
|
"""
|
|
371
379
|
global _oauth_result
|
|
372
380
|
_oauth_result = {"code": None, "error": None}
|
|
373
381
|
|
|
374
382
|
store = MCPServerStore()
|
|
375
383
|
server = store.get(name)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
384
|
+
if server is None:
|
|
385
|
+
server = known_server_config(name)
|
|
386
|
+
if server is None:
|
|
387
|
+
console.print(f"[red]Server '{name}' not found.[/red]")
|
|
388
|
+
console.print(
|
|
389
|
+
"[dim]Add it with 'cade mcp add <name> <url>', "
|
|
390
|
+
"or authorize a known server like 'omni'.[/dim]"
|
|
391
|
+
)
|
|
392
|
+
return False
|
|
393
|
+
store.add(server) # persisted disabled; enabled on success
|
|
380
394
|
|
|
381
395
|
if client_id:
|
|
382
396
|
server.oauth_client_id = client_id
|
|
@@ -387,11 +401,10 @@ def authorize_server(
|
|
|
387
401
|
manager = MCPToolManager(server, store)
|
|
388
402
|
|
|
389
403
|
try:
|
|
390
|
-
#
|
|
404
|
+
# Initialize to trigger the 401 that advertises the auth server.
|
|
391
405
|
console.print("[cyan]Discovering authorization server...[/cyan]")
|
|
392
406
|
|
|
393
407
|
try:
|
|
394
|
-
# Make a test request to get 401
|
|
395
408
|
await manager._send_request(
|
|
396
409
|
"initialize",
|
|
397
410
|
{
|
|
@@ -401,6 +414,8 @@ def authorize_server(
|
|
|
401
414
|
},
|
|
402
415
|
)
|
|
403
416
|
console.print("[yellow]Server doesn't require authentication.[/yellow]")
|
|
417
|
+
server.enabled = True
|
|
418
|
+
store.add(server)
|
|
404
419
|
return True
|
|
405
420
|
except Exception as e:
|
|
406
421
|
if "ToolAuthorizationRequired" not in str(type(e).__name__):
|
|
@@ -411,7 +426,7 @@ def authorize_server(
|
|
|
411
426
|
console.print("[red]Failed to discover authorization server.[/red]")
|
|
412
427
|
return False
|
|
413
428
|
|
|
414
|
-
# Perform DCR if
|
|
429
|
+
# Perform DCR if no client_id is provided/configured.
|
|
415
430
|
effective_client_id = client_id or server.oauth_client_id
|
|
416
431
|
if not effective_client_id and not skip_dcr:
|
|
417
432
|
registration_endpoint = manager._auth_server_metadata.get("registration_endpoint")
|
|
@@ -447,19 +462,18 @@ def authorize_server(
|
|
|
447
462
|
console.print("[yellow]Tip: Provide a client_id with --client-id[/yellow]")
|
|
448
463
|
return False
|
|
449
464
|
|
|
450
|
-
# Start local callback server
|
|
465
|
+
# Start local callback server (redirect_uri is 127.0.0.1:9876/callback).
|
|
451
466
|
server_address = ("127.0.0.1", 9876)
|
|
452
467
|
httpd = http.server.HTTPServer(server_address, OAuthCallbackHandler)
|
|
453
468
|
httpd.timeout = 300 # 5 minute timeout
|
|
454
469
|
|
|
455
|
-
# Generate new OAuth flow with proper PKCE
|
|
456
470
|
auth_url, code_verifier, state = await manager._oauth_handler.start_oauth_flow(
|
|
457
471
|
manager._auth_server_metadata,
|
|
458
472
|
scope,
|
|
459
473
|
effective_client_id,
|
|
460
474
|
)
|
|
461
|
-
#
|
|
462
|
-
#
|
|
475
|
+
# Match the manager's pending verifier to this auth flow (the initial
|
|
476
|
+
# 401 discovery generated a different verifier).
|
|
463
477
|
manager._pending_code_verifier = code_verifier
|
|
464
478
|
manager._pending_state = state
|
|
465
479
|
|
|
@@ -469,7 +483,6 @@ def authorize_server(
|
|
|
469
483
|
|
|
470
484
|
console.print("[yellow]Waiting for authorization (5 min timeout)...[/yellow]")
|
|
471
485
|
|
|
472
|
-
# Handle one request
|
|
473
486
|
def handle_callback() -> None:
|
|
474
487
|
httpd.handle_request()
|
|
475
488
|
|
|
@@ -485,22 +498,90 @@ def authorize_server(
|
|
|
485
498
|
console.print("[red]No authorization code received.[/red]")
|
|
486
499
|
return False
|
|
487
500
|
|
|
488
|
-
# Exchange code for tokens
|
|
489
501
|
console.print("[cyan]Exchanging code for tokens...[/cyan]")
|
|
490
502
|
success = await manager.complete_oauth_flow(_oauth_result["code"])
|
|
491
503
|
|
|
492
504
|
if success:
|
|
505
|
+
server.enabled = True
|
|
506
|
+
store.add(server)
|
|
493
507
|
console.print(f"[green]✓[/green] Successfully authorized '{name}'")
|
|
494
508
|
return True
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return False
|
|
509
|
+
console.print("[red]Failed to exchange code for tokens.[/red]")
|
|
510
|
+
return False
|
|
498
511
|
|
|
499
512
|
finally:
|
|
500
513
|
await manager.close()
|
|
501
514
|
|
|
502
|
-
|
|
503
|
-
|
|
515
|
+
return asyncio.run(run_oauth_flow())
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def offer_connect_omni(target_console: Console | None = None) -> None:
|
|
519
|
+
"""Offer to connect Arcade Omni (external tools) if not already authorized.
|
|
520
|
+
|
|
521
|
+
Called after ``cade login`` and after first-run onboarding. No-op when the
|
|
522
|
+
terminal isn't interactive or when Omni is already authorized, so it never
|
|
523
|
+
blocks scripted/CI use.
|
|
524
|
+
"""
|
|
525
|
+
con = target_console or console
|
|
526
|
+
|
|
527
|
+
from cadecoder.cli.onboarding import can_prompt
|
|
528
|
+
|
|
529
|
+
if not can_prompt():
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
store = MCPServerStore() # get_runtime() auto-builds + ensures an agent
|
|
534
|
+
except Exception: # noqa: BLE001
|
|
535
|
+
return
|
|
536
|
+
|
|
537
|
+
existing = store.get("omni")
|
|
538
|
+
if existing is not None and existing.oauth_tokens is not None:
|
|
539
|
+
return # already connected
|
|
540
|
+
|
|
541
|
+
from rich.prompt import Confirm
|
|
542
|
+
|
|
543
|
+
con.print()
|
|
544
|
+
con.print(
|
|
545
|
+
"[bold]Connect external tools?[/bold] Arcade [cyan]Omni[/cyan] lets Cade use "
|
|
546
|
+
"Gmail, Slack, GitHub, web search and more — authorized with your Arcade login "
|
|
547
|
+
"(no API key needed)."
|
|
548
|
+
)
|
|
549
|
+
try:
|
|
550
|
+
proceed = Confirm.ask("Authorize Omni now? (opens your browser)", default=True, console=con)
|
|
551
|
+
except (KeyboardInterrupt, EOFError):
|
|
552
|
+
con.print()
|
|
553
|
+
return
|
|
554
|
+
|
|
555
|
+
if not proceed:
|
|
556
|
+
con.print("[dim]Skipped. Connect anytime with [bold]cade mcp authorize omni[/bold].[/dim]")
|
|
557
|
+
return
|
|
558
|
+
|
|
559
|
+
if not run_mcp_authorization("omni"):
|
|
560
|
+
con.print("[dim]You can retry later with [bold]cade mcp authorize omni[/bold].[/dim]")
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@mcp_app.command("authorize")
|
|
564
|
+
def authorize_server(
|
|
565
|
+
name: Annotated[str, typer.Argument(help="Name of the server to authorize")],
|
|
566
|
+
client_id: Annotated[
|
|
567
|
+
str | None,
|
|
568
|
+
typer.Option("--client-id", "-c", help="OAuth client ID (auto-registers if not provided)"),
|
|
569
|
+
] = None,
|
|
570
|
+
scope: Annotated[
|
|
571
|
+
str | None,
|
|
572
|
+
typer.Option("--scope", "-s", help="OAuth scopes (space-separated)"),
|
|
573
|
+
] = None,
|
|
574
|
+
skip_dcr: Annotated[
|
|
575
|
+
bool,
|
|
576
|
+
typer.Option("--skip-dcr", help="Skip Dynamic Client Registration"),
|
|
577
|
+
] = False,
|
|
578
|
+
) -> None:
|
|
579
|
+
"""Authorize an MCP server over OAuth (browser flow).
|
|
580
|
+
|
|
581
|
+
Works with any configured server, or a built-in known server by name
|
|
582
|
+
(e.g. ``cade mcp authorize omni`` to connect Arcade's external tools).
|
|
583
|
+
"""
|
|
584
|
+
if not run_mcp_authorization(name, client_id=client_id, scope=scope, skip_dcr=skip_dcr):
|
|
504
585
|
raise typer.Exit(1)
|
|
505
586
|
|
|
506
587
|
|
|
@@ -7,11 +7,13 @@ from cadecoder.tools.manager.base import (
|
|
|
7
7
|
)
|
|
8
8
|
from cadecoder.tools.manager.composite import CompositeToolManager
|
|
9
9
|
from cadecoder.tools.manager.config import (
|
|
10
|
+
KNOWN_SERVERS,
|
|
10
11
|
MCPAuthType,
|
|
11
12
|
MCPOAuthTokens,
|
|
12
13
|
MCPServerConfig,
|
|
13
14
|
MCPServerStore,
|
|
14
15
|
MCPTransportType,
|
|
16
|
+
known_server_config,
|
|
15
17
|
)
|
|
16
18
|
from cadecoder.tools.manager.mcp import MCPOAuthHandler, MCPToolManager
|
|
17
19
|
|
|
@@ -30,4 +32,7 @@ __all__ = [
|
|
|
30
32
|
"MCPAuthType",
|
|
31
33
|
"MCPOAuthTokens",
|
|
32
34
|
"MCPTransportType",
|
|
35
|
+
# Known remote servers
|
|
36
|
+
"KNOWN_SERVERS",
|
|
37
|
+
"known_server_config",
|
|
33
38
|
]
|
|
@@ -147,6 +147,40 @@ class MCPServerConfig:
|
|
|
147
147
|
)
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
# Built-in, well-known remote MCP servers a user can connect with one command
|
|
151
|
+
# (`cade mcp authorize <name>`). They are *not* added to an agent until the user
|
|
152
|
+
# authorizes them, so they never break a fresh install. Omni is Arcade's
|
|
153
|
+
# meta-discovery server: once authorized over OAuth it exposes the whole Arcade
|
|
154
|
+
# tool catalog (Gmail, Slack, GitHub, web search, …) via SelectTools/UseTool —
|
|
155
|
+
# no ARCADE_API_KEY required (auth rides the user's Arcade login).
|
|
156
|
+
KNOWN_SERVERS: dict[str, dict[str, str]] = {
|
|
157
|
+
"omni": {
|
|
158
|
+
"url": "https://omni.arcade.dev/mcp",
|
|
159
|
+
"description": (
|
|
160
|
+
"Arcade Omni — discover & run external tools (Gmail, Slack, GitHub, web search, …)"
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def known_server_config(name: str) -> "MCPServerConfig | None":
|
|
167
|
+
"""Return a fresh, disabled OAuth config for a built-in known server.
|
|
168
|
+
|
|
169
|
+
Disabled by default so it is only connected after a successful
|
|
170
|
+
``cade mcp authorize <name>`` (which flips ``enabled`` on).
|
|
171
|
+
"""
|
|
172
|
+
info = KNOWN_SERVERS.get(name)
|
|
173
|
+
if info is None:
|
|
174
|
+
return None
|
|
175
|
+
return MCPServerConfig(
|
|
176
|
+
name=name,
|
|
177
|
+
url=info["url"],
|
|
178
|
+
transport=MCPTransportType.HTTP,
|
|
179
|
+
auth_type=MCPAuthType.OAUTH,
|
|
180
|
+
enabled=False,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
150
184
|
class MCPServerStore:
|
|
151
185
|
"""Persistent storage for the current agent's MCP server configurations.
|
|
152
186
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|