cade-cli 0.15.1__tar.gz → 0.15.4__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.
Files changed (165) hide show
  1. {cade_cli-0.15.1 → cade_cli-0.15.4}/PKG-INFO +2 -2
  2. {cade_cli-0.15.1 → cade_cli-0.15.4}/README.md +1 -1
  3. {cade_cli-0.15.1 → cade_cli-0.15.4}/pyproject.toml +6 -1
  4. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/server.py +26 -2
  5. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/app.py +12 -1
  6. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/auth.py +10 -0
  7. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/mcp.py +124 -43
  8. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/manager/__init__.py +5 -0
  9. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/manager/config.py +34 -0
  10. {cade_cli-0.15.1 → cade_cli-0.15.4}/.gitignore +0 -0
  11. {cade_cli-0.15.1 → cade_cli-0.15.4}/LICENSE +0 -0
  12. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/__init__.py +0 -0
  13. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/__main__.py +0 -0
  14. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/_metadata.py +0 -0
  15. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/config.py +0 -0
  16. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/errors.py +0 -0
  17. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/resources.py +0 -0
  18. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/__init__.py +0 -0
  19. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  20. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/agents.py +0 -0
  21. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/context.py +0 -0
  22. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/filesystem.py +0 -0
  23. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/git.py +0 -0
  24. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/notifications.py +0 -0
  25. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/questions.py +0 -0
  26. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/results.py +0 -0
  27. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/schemas.py +0 -0
  28. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/search.py +0 -0
  29. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/shell.py +0 -0
  30. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/snippets.py +0 -0
  31. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/tasks.py +0 -0
  32. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  33. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/tools/web_search.py +0 -0
  34. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/utils.py +0 -0
  35. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/__init__.py +0 -0
  36. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/arcade_client.py +0 -0
  37. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/cache.py +0 -0
  38. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/extract.py +0 -0
  39. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/markdown.py +0 -0
  40. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/preapproved.py +0 -0
  41. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/prompts.py +0 -0
  42. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/scrape.py +0 -0
  43. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/search/__init__.py +0 -0
  44. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/search/arcade.py +0 -0
  45. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/search/base.py +0 -0
  46. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cade_mcp_local/web/search/http.py +0 -0
  47. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/__init__.py +0 -0
  48. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ai/__init__.py +0 -0
  49. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ai/prompts.py +0 -0
  50. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/__init__.py +0 -0
  51. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/auth.py +0 -0
  52. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/__init__.py +0 -0
  53. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/account.py +0 -0
  54. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/agents.py +0 -0
  55. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/chat.py +0 -0
  56. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/cron.py +0 -0
  57. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/hooks.py +0 -0
  58. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/mem.py +0 -0
  59. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/serve.py +0 -0
  60. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/tasks.py +0 -0
  61. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/thread.py +0 -0
  62. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/commands/tools.py +0 -0
  63. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/cli/onboarding.py +0 -0
  64. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/__init__.py +0 -0
  65. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/agent.py +0 -0
  66. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/comms.py +0 -0
  67. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/config.py +0 -0
  68. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/constants.py +0 -0
  69. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/errors.py +0 -0
  70. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/frozen.py +0 -0
  71. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/git.py +0 -0
  72. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/inbox.py +0 -0
  73. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/logging.py +0 -0
  74. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/memory.py +0 -0
  75. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/model_specs.py +0 -0
  76. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/names.py +0 -0
  77. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/paths.py +0 -0
  78. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/runtime.py +0 -0
  79. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/secrets.py +0 -0
  80. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/spawn.py +0 -0
  81. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/core/types.py +0 -0
  82. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/execution/__init__.py +0 -0
  83. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/execution/context_window.py +0 -0
  84. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  85. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/execution/orchestrator.py +0 -0
  86. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/execution/parallel.py +0 -0
  87. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/execution/tool_result_store.py +0 -0
  88. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  89. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/__init__.py +0 -0
  90. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/config.py +0 -0
  91. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/engine.py +0 -0
  92. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/executors/__init__.py +0 -0
  93. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/executors/callback.py +0 -0
  94. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/executors/command.py +0 -0
  95. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/executors/http.py +0 -0
  96. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/executors/prompt.py +0 -0
  97. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  98. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/matchers.py +0 -0
  99. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/registry.py +0 -0
  100. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/hooks/types.py +0 -0
  101. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/providers/__init__.py +0 -0
  102. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/providers/anthropic.py +0 -0
  103. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/providers/base.py +0 -0
  104. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/providers/ollama.py +0 -0
  105. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/providers/openai.py +0 -0
  106. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/__init__.py +0 -0
  107. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/adapters/__init__.py +0 -0
  108. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/adapters/base.py +0 -0
  109. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/adapters/desktop.py +0 -0
  110. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/adapters/registry.py +0 -0
  111. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/adapters/stub.py +0 -0
  112. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/adapters/telegram.py +0 -0
  113. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/allowlist.py +0 -0
  114. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/chunker.py +0 -0
  115. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/commands.py +0 -0
  116. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/config.py +0 -0
  117. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/cron.py +0 -0
  118. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/daemon.py +0 -0
  119. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/elicitation.py +0 -0
  120. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/format.py +0 -0
  121. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/install.py +0 -0
  122. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/lock.py +0 -0
  123. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/observability.py +0 -0
  124. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/owner.py +0 -0
  125. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/progress_sink.py +0 -0
  126. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/router.py +0 -0
  127. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/secrets.py +0 -0
  128. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/services.py +0 -0
  129. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/sinks.py +0 -0
  130. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/serve/worker.py +0 -0
  131. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/storage/__init__.py +0 -0
  132. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/storage/threads.py +0 -0
  133. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tasks/__init__.py +0 -0
  134. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tasks/lock.py +0 -0
  135. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tasks/scheduler.py +0 -0
  136. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tasks/store.py +0 -0
  137. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tasks/types.py +0 -0
  138. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/templates/login_failed.html +0 -0
  139. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/templates/login_success.html +0 -0
  140. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  141. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/templates/serve/serve.toml.example +0 -0
  142. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  143. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/templates/styles.css +0 -0
  144. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/__init__.py +0 -0
  145. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  146. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/manager/base.py +0 -0
  147. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/manager/composite.py +0 -0
  148. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/manager/mcp.py +0 -0
  149. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/search/__init__.py +0 -0
  150. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/search/discovered.py +0 -0
  151. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/search/scoring.py +0 -0
  152. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/tools/search/service.py +0 -0
  153. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ui/__init__.py +0 -0
  154. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ui/completion.py +0 -0
  155. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ui/display.py +0 -0
  156. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ui/elicitation.py +0 -0
  157. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ui/input.py +0 -0
  158. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ui/session.py +0 -0
  159. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/ui/slash.py +0 -0
  160. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/voice/__init__.py +0 -0
  161. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/voice/audio.py +0 -0
  162. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/voice/cleanup.py +0 -0
  163. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/voice/session.py +0 -0
  164. {cade_cli-0.15.1 → cade_cli-0.15.4}/src/cadecoder/voice/stt.py +0 -0
  165. {cade_cli-0.15.1 → cade_cli-0.15.4}/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.1
3
+ Version: 0.15.4
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.1 CADE_INSTALL_DIR=/usr/local/bin \
126
+ CADE_VERSION=0.15.4 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.1 CADE_INSTALL_DIR=/usr/local/bin \
38
+ CADE_VERSION=0.15.4 CADE_INSTALL_DIR=/usr/local/bin \
39
39
  sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
40
40
  ```
41
41
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.15.1"
3
+ version = "0.15.4"
4
4
  description = "Cade - The CLI Agent from Arcade.dev"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -75,6 +75,11 @@ voice = [
75
75
  "sounddevice>=0.5.5,<1.0.0",
76
76
  "numpy>=1.24.0",
77
77
  "webrtcvad>=2.0.10",
78
+ # NOTE: Kokoro TTS also needs spaCy's en_core_web_sm model, but it is NOT
79
+ # listed here — a direct-URL dependency in package metadata is rejected by
80
+ # PyPI on upload. The macOS release build installs it explicitly before
81
+ # PyInstaller (see .github/workflows/release.yml), and cade.spec bundles it
82
+ # so misaki never downloads it at runtime in the frozen app.
78
83
  ]
79
84
  training = [
80
85
  "torch>=2.1.0",
@@ -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 (read external pages, search the open web)
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
- app.add_tool(web_search_tool)
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)
@@ -458,8 +458,9 @@ def main_callback(
458
458
  # when not attached to an interactive terminal (piped/CI).
459
459
  from cadecoder.cli.onboarding import needs_onboarding, run_onboarding
460
460
 
461
+ did_onboard = False
461
462
  if needs_onboarding():
462
- run_onboarding(console)
463
+ did_onboard = run_onboarding(console)
463
464
 
464
465
  # Interaction + runtime-backed commands run as the current agent, auto-creating
465
466
  # a `default` agent on first run via build_runtime → ensure_current_agent.
@@ -467,6 +468,16 @@ def main_callback(
467
468
  set_runtime(runtime)
468
469
  ctx.obj["runtime"] = runtime
469
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
+
470
481
  # Handle single message mode
471
482
  if message is not None and ctx.invoked_subcommand is None:
472
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 != MCPAuthType.NONE:
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
- @mcp_app.command("authorize")
347
- def authorize_server(
348
- name: Annotated[str, typer.Argument(help="Name of the server to authorize")],
349
- client_id: Annotated[
350
- str | None,
351
- typer.Option("--client-id", "-c", help="OAuth client ID (auto-registers if not provided)"),
352
- ] = None,
353
- scope: Annotated[
354
- str | None,
355
- typer.Option("--scope", "-s", help="OAuth scopes (space-separated)"),
356
- ] = None,
357
- skip_dcr: Annotated[
358
- bool,
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
- if not server:
378
- console.print(f"[red]Server '{name}' not found.[/red]")
379
- raise typer.Exit(1)
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
- # First, try to initialize to trigger 401 and discover auth server
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 needed (no client_id provided or configured)
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
- # Update the manager's pending verifier to match this auth flow
462
- # (the initial 401 discovery generated a different verifier)
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
- else:
496
- console.print("[red]Failed to exchange code for tokens.[/red]")
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
- success = asyncio.run(run_oauth_flow())
503
- if not success:
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