deepy-cli 0.1.13__tar.gz → 0.2.0__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 (81) hide show
  1. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/PKG-INFO +76 -7
  2. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/README.md +74 -6
  3. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/pyproject.toml +2 -1
  4. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/__init__.py +1 -1
  5. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/config/__init__.py +6 -0
  6. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/config/settings.py +85 -0
  7. deepy_cli-0.2.0/src/deepy/data/skills/skill-creator/SKILL.md +54 -0
  8. deepy_cli-0.2.0/src/deepy/data/skills/skill-installer/SKILL.md +34 -0
  9. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/WebSearch.md +5 -0
  10. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/agent.py +14 -2
  11. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/events.py +11 -1
  12. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/runner.py +71 -18
  13. deepy_cli-0.2.0/src/deepy/mcp.py +561 -0
  14. deepy_cli-0.2.0/src/deepy/prompts/init_agents.py +36 -0
  15. deepy_cli-0.2.0/src/deepy/prompts/rules.py +139 -0
  16. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/prompts/system.py +43 -3
  17. deepy_cli-0.2.0/src/deepy/skill_market.py +350 -0
  18. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/skills.py +26 -8
  19. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/status.py +9 -0
  20. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/tools/agents.py +47 -6
  21. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/tools/builtin.py +33 -45
  22. deepy_cli-0.2.0/src/deepy/tools/shell_output.py +45 -0
  23. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/local_command.py +15 -4
  24. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/message_view.py +113 -0
  25. deepy_cli-0.2.0/src/deepy/ui/skill_picker.py +433 -0
  26. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/slash_commands.py +13 -7
  27. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/terminal.py +538 -115
  28. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/welcome.py +1 -1
  29. deepy_cli-0.1.13/src/deepy/prompts/rules.py +0 -24
  30. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/__main__.py +0 -0
  31. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/cli.py +0 -0
  32. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/__init__.py +0 -0
  33. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  34. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/WebFetch.md +0 -0
  35. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/__init__.py +0 -0
  36. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/edit.md +0 -0
  37. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/modify.md +0 -0
  38. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/read.md +0 -0
  39. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/shell.md +0 -0
  40. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/data/tools/write.md +0 -0
  41. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/errors.py +0 -0
  42. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/__init__.py +0 -0
  43. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/compaction.py +0 -0
  44. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/context.py +0 -0
  45. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/model_capabilities.py +0 -0
  46. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/provider.py +0 -0
  47. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/replay.py +0 -0
  48. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/llm/thinking.py +0 -0
  49. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/prompts/__init__.py +0 -0
  50. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/prompts/compact.py +0 -0
  51. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/prompts/runtime_context.py +0 -0
  52. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/prompts/tool_docs.py +0 -0
  53. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/sessions/__init__.py +0 -0
  54. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/sessions/jsonl.py +0 -0
  55. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/sessions/manager.py +0 -0
  56. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/tools/__init__.py +0 -0
  57. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/tools/file_state.py +0 -0
  58. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/tools/result.py +0 -0
  59. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/tools/shell_utils.py +0 -0
  60. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/__init__.py +0 -0
  61. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/app.py +0 -0
  62. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/ask_user_question.py +0 -0
  63. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/exit_summary.py +0 -0
  64. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/file_mentions.py +0 -0
  65. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/loading_text.py +0 -0
  66. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/markdown.py +0 -0
  67. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/model_picker.py +0 -0
  68. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/prompt_buffer.py +0 -0
  69. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/prompt_input.py +0 -0
  70. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/session_list.py +0 -0
  71. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/session_picker.py +0 -0
  72. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/styles.py +0 -0
  73. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/theme_picker.py +0 -0
  74. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/ui/thinking_state.py +0 -0
  75. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/update_check.py +0 -0
  76. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/usage.py +0 -0
  77. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/utils/__init__.py +0 -0
  78. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/utils/debug_logger.py +0 -0
  79. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/utils/error_logger.py +0 -0
  80. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/utils/json.py +0 -0
  81. {deepy_cli-0.1.13 → deepy_cli-0.2.0}/src/deepy/utils/notify.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: deepy-cli
3
- Version: 0.1.13
3
+ Version: 0.2.0
4
4
  Summary: Deepy - Vibe coding for DeepSeek models in your terminal
5
5
  Keywords: deepseek,coding-agent,terminal,cli,agents
6
6
  Author: kirineko
@@ -17,6 +17,7 @@ Requires-Dist: openai>=2.26,<3
17
17
  Requires-Dist: orjson>=3.10,<4
18
18
  Requires-Dist: pydantic>=2.12,<3
19
19
  Requires-Dist: prompt-toolkit>=3.0,<4
20
+ Requires-Dist: pyyaml>=6.0,<7
20
21
  Requires-Dist: rich>=13.9,<15
21
22
  Requires-Dist: tiktoken>=0.9,<1
22
23
  Requires-Dist: tomli-w>=1
@@ -268,6 +269,30 @@ WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it:
268
269
  searxng_url = "https://your-searxng.example/"
269
270
  ```
270
271
 
272
+ Deepy can also load MCP servers through the OpenAI Agents SDK. Most users only
273
+ need `~/.deepy/mcp.json`:
274
+
275
+ ```json
276
+ {
277
+ "mcpServers": {
278
+ "tavily": {
279
+ "transport": "stdio",
280
+ "command": "npx",
281
+ "args": ["-y", "tavily-mcp"],
282
+ "env": {"TAVILY_API_KEY": "${TAVILY_API_KEY}"},
283
+ "roles": ["web_search"]
284
+ }
285
+ }
286
+ }
287
+ ```
288
+
289
+ When an active MCP tool is marked or detected as web search, Deepy instructs the
290
+ model to prefer it over built-in WebSearch and keeps built-in WebSearch as a
291
+ fallback. Project MCP config is ignored by default because stdio MCP servers can
292
+ start local commands; enable `mcp.allow_project_config` only for trusted
293
+ projects. Use `/mcp` to inspect server status and exposed tools. Advanced MCP
294
+ configuration is documented in [docs/mcp.md](docs/mcp.md).
295
+
271
296
  ## Command Reference
272
297
 
273
298
  ```bash
@@ -284,15 +309,59 @@ deepy sessions show <session-id>
284
309
  deepy run "summarize this project"
285
310
  ```
286
311
 
287
- ## Project Rules And Skills
312
+ Inside the interactive terminal:
313
+
314
+ ```text
315
+ /skills Manage local and market skills
316
+ /skills list List discovered skills
317
+ /skills search <query> Search the configured skill market
318
+ /skills install <name> Install a market skill
319
+ /skill:<name> [request] Invoke a skill directly
320
+ /init Create or update project AGENTS.md
321
+ /mcp Show MCP server status and tools
322
+ ```
323
+
324
+ ## AGENTS.md Instructions And Skills
325
+
326
+ Deepy automatically loads agent-facing instructions from:
327
+
328
+ - `~/.deepy/AGENTS.md` for Deepy-wide personal guidance
329
+ - `AGENTS.md` files from the git root down to the current working directory
330
+ - `.agents/skills/*/SKILL.md`
288
331
 
289
- Deepy automatically loads project instructions from:
332
+ Project `AGENTS.md` files are loaded from broad to specific. A file in a nested
333
+ directory appears after the repository root file and takes precedence when rules
334
+ conflict. Direct user instructions still take precedence over loaded
335
+ `AGENTS.md` guidance.
336
+
337
+ A concise `AGENTS.md` works best:
338
+
339
+ ```markdown
340
+ # AGENTS.md
341
+
342
+ ## Commands
343
+ - Test: `uv run pytest`
344
+ - Lint: `uv run ruff check`
345
+ - Type check: `uv run pyright`
346
+
347
+ ## Architecture
348
+ - Keep CLI entry points thin; put reusable behavior under `src/`.
349
+
350
+ ## Style
351
+ - Prefer small, focused changes that match existing patterns.
352
+
353
+ ## Verification
354
+ - Run focused tests for touched code before broader checks.
355
+
356
+ ## Boundaries
357
+ - Do not rewrite unrelated files or revert user changes.
358
+ ```
290
359
 
291
- - `AGENTS.md`
292
- - `.deepy/skills/*/SKILL.md`
360
+ Skills remain standard Agent Skills under `.agents/skills/*/SKILL.md` and are
361
+ loaded through the skill progressive-disclosure flow.
293
362
 
294
- This lets each repository define local conventions, commands, review rules, and
295
- domain-specific workflows without changing global config.
363
+ Run `/init` in the interactive terminal to have Deepy inspect the repository and
364
+ create or refresh the project root `AGENTS.md`.
296
365
 
297
366
  ## Development
298
367
 
@@ -240,6 +240,30 @@ WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it:
240
240
  searxng_url = "https://your-searxng.example/"
241
241
  ```
242
242
 
243
+ Deepy can also load MCP servers through the OpenAI Agents SDK. Most users only
244
+ need `~/.deepy/mcp.json`:
245
+
246
+ ```json
247
+ {
248
+ "mcpServers": {
249
+ "tavily": {
250
+ "transport": "stdio",
251
+ "command": "npx",
252
+ "args": ["-y", "tavily-mcp"],
253
+ "env": {"TAVILY_API_KEY": "${TAVILY_API_KEY}"},
254
+ "roles": ["web_search"]
255
+ }
256
+ }
257
+ }
258
+ ```
259
+
260
+ When an active MCP tool is marked or detected as web search, Deepy instructs the
261
+ model to prefer it over built-in WebSearch and keeps built-in WebSearch as a
262
+ fallback. Project MCP config is ignored by default because stdio MCP servers can
263
+ start local commands; enable `mcp.allow_project_config` only for trusted
264
+ projects. Use `/mcp` to inspect server status and exposed tools. Advanced MCP
265
+ configuration is documented in [docs/mcp.md](docs/mcp.md).
266
+
243
267
  ## Command Reference
244
268
 
245
269
  ```bash
@@ -256,15 +280,59 @@ deepy sessions show <session-id>
256
280
  deepy run "summarize this project"
257
281
  ```
258
282
 
259
- ## Project Rules And Skills
283
+ Inside the interactive terminal:
284
+
285
+ ```text
286
+ /skills Manage local and market skills
287
+ /skills list List discovered skills
288
+ /skills search <query> Search the configured skill market
289
+ /skills install <name> Install a market skill
290
+ /skill:<name> [request] Invoke a skill directly
291
+ /init Create or update project AGENTS.md
292
+ /mcp Show MCP server status and tools
293
+ ```
294
+
295
+ ## AGENTS.md Instructions And Skills
296
+
297
+ Deepy automatically loads agent-facing instructions from:
298
+
299
+ - `~/.deepy/AGENTS.md` for Deepy-wide personal guidance
300
+ - `AGENTS.md` files from the git root down to the current working directory
301
+ - `.agents/skills/*/SKILL.md`
260
302
 
261
- Deepy automatically loads project instructions from:
303
+ Project `AGENTS.md` files are loaded from broad to specific. A file in a nested
304
+ directory appears after the repository root file and takes precedence when rules
305
+ conflict. Direct user instructions still take precedence over loaded
306
+ `AGENTS.md` guidance.
307
+
308
+ A concise `AGENTS.md` works best:
309
+
310
+ ```markdown
311
+ # AGENTS.md
312
+
313
+ ## Commands
314
+ - Test: `uv run pytest`
315
+ - Lint: `uv run ruff check`
316
+ - Type check: `uv run pyright`
317
+
318
+ ## Architecture
319
+ - Keep CLI entry points thin; put reusable behavior under `src/`.
320
+
321
+ ## Style
322
+ - Prefer small, focused changes that match existing patterns.
323
+
324
+ ## Verification
325
+ - Run focused tests for touched code before broader checks.
326
+
327
+ ## Boundaries
328
+ - Do not rewrite unrelated files or revert user changes.
329
+ ```
262
330
 
263
- - `AGENTS.md`
264
- - `.deepy/skills/*/SKILL.md`
331
+ Skills remain standard Agent Skills under `.agents/skills/*/SKILL.md` and are
332
+ loaded through the skill progressive-disclosure flow.
265
333
 
266
- This lets each repository define local conventions, commands, review rules, and
267
- domain-specific workflows without changing global config.
334
+ Run `/init` in the interactive terminal to have Deepy inspect the repository and
335
+ create or refresh the project root `AGENTS.md`.
268
336
 
269
337
  ## Development
270
338
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.1.13"
3
+ version = "0.2.0"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -23,6 +23,7 @@ dependencies = [
23
23
  "orjson>=3.10,<4",
24
24
  "pydantic>=2.12,<3",
25
25
  "prompt-toolkit>=3.0,<4",
26
+ "pyyaml>=6.0,<7",
26
27
  "rich>=13.9,<15",
27
28
  "tiktoken>=0.9,<1",
28
29
  "tomli-w>=1",
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.1.13"
3
+ __version__ = "0.2.0"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -8,6 +8,8 @@ from .settings import (
8
8
  DEFAULT_UI_THEME,
9
9
  DEFAULT_WEB_SEARCH_SEARXNG_URL,
10
10
  DeepSeekModelInfo,
11
+ McpConfig,
12
+ McpWebSearchConfig,
11
13
  ModelConfig,
12
14
  REASONING_MODES,
13
15
  Settings,
@@ -16,6 +18,7 @@ from .settings import (
16
18
  UI_THEMES,
17
19
  UiConfig,
18
20
  default_config_path,
21
+ default_mcp_config_path,
19
22
  is_supported_deepseek_model,
20
23
  is_valid_ui_theme,
21
24
  is_valid_reasoning_mode,
@@ -37,6 +40,8 @@ __all__ = [
37
40
  "DEFAULT_UI_THEME",
38
41
  "DEFAULT_WEB_SEARCH_SEARXNG_URL",
39
42
  "DeepSeekModelInfo",
43
+ "McpConfig",
44
+ "McpWebSearchConfig",
40
45
  "ModelConfig",
41
46
  "REASONING_MODES",
42
47
  "Settings",
@@ -45,6 +50,7 @@ __all__ = [
45
50
  "UI_THEMES",
46
51
  "UiConfig",
47
52
  "default_config_path",
53
+ "default_mcp_config_path",
48
54
  "is_supported_deepseek_model",
49
55
  "is_valid_ui_theme",
50
56
  "is_valid_reasoning_mode",
@@ -16,6 +16,11 @@ DEFAULT_RESERVED_CONTEXT_TOKENS = 50_000
16
16
  DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES = 2
17
17
  DEFAULT_WEB_SEARCH_SEARXNG_URL = "https://s.kirineko.tech/"
18
18
  DEFAULT_UI_THEME = "auto"
19
+ DEFAULT_MCP_ENABLED = True
20
+ DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS = 10.0
21
+ DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS = 10.0
22
+ DEFAULT_MCP_CLIENT_SESSION_TIMEOUT_SECONDS = 30.0
23
+ DEFAULT_MCP_CACHE_TOOLS_LIST = True
19
24
  REASONING_EFFORTS = {"high", "max"}
20
25
  REASONING_MODES = {"none", "high", "max"}
21
26
  UI_THEMES = {"auto", "dark", "light"}
@@ -50,6 +55,12 @@ def default_config_path() -> Path:
50
55
  return Path.home() / ".deepy" / "config.toml"
51
56
 
52
57
 
58
+ def default_mcp_config_path(config_path: Path | None = None) -> Path:
59
+ if config_path is not None:
60
+ return config_path.expanduser().parent / "mcp.json"
61
+ return Path.home() / ".deepy" / "mcp.json"
62
+
63
+
53
64
  def mask_secret(value: str | None) -> str:
54
65
  if not value:
55
66
  return ""
@@ -212,6 +223,63 @@ class ToolsConfig:
212
223
  return cls(web_search=WebSearchToolConfig.from_mapping(_as_mapping(raw.get("web_search"))))
213
224
 
214
225
 
226
+ @dataclass(frozen=True)
227
+ class McpWebSearchConfig:
228
+ prefer_mcp: bool = True
229
+ preferred_server: str | None = None
230
+ preferred_tools: tuple[str, ...] = ()
231
+ fallback_to_builtin: bool = True
232
+
233
+ @classmethod
234
+ def from_mapping(cls, raw: Mapping[str, Any]) -> Self:
235
+ tools = raw.get("preferred_tools")
236
+ preferred_tools = (
237
+ tuple(item.strip() for item in tools if isinstance(item, str) and item.strip())
238
+ if isinstance(tools, list)
239
+ else ()
240
+ )
241
+ return cls(
242
+ prefer_mcp=_as_bool(raw.get("prefer_mcp"), True),
243
+ preferred_server=_as_str(raw.get("preferred_server")) or None,
244
+ preferred_tools=preferred_tools,
245
+ fallback_to_builtin=_as_bool(raw.get("fallback_to_builtin"), True),
246
+ )
247
+
248
+
249
+ @dataclass(frozen=True)
250
+ class McpConfig:
251
+ enabled: bool = DEFAULT_MCP_ENABLED
252
+ connect_timeout_seconds: float = DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS
253
+ cleanup_timeout_seconds: float = DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS
254
+ client_session_timeout_seconds: float = DEFAULT_MCP_CLIENT_SESSION_TIMEOUT_SECONDS
255
+ cache_tools_list: bool = DEFAULT_MCP_CACHE_TOOLS_LIST
256
+ allow_project_config: bool = False
257
+ prefer_mcp_web_search: bool = True
258
+ web_search: McpWebSearchConfig = field(default_factory=McpWebSearchConfig)
259
+
260
+ @classmethod
261
+ def from_mapping(cls, raw: Mapping[str, Any]) -> Self:
262
+ return cls(
263
+ enabled=_as_bool(raw.get("enabled"), DEFAULT_MCP_ENABLED),
264
+ connect_timeout_seconds=_as_float(
265
+ raw.get("connect_timeout_seconds"),
266
+ DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS,
267
+ ),
268
+ cleanup_timeout_seconds=_as_float(
269
+ raw.get("cleanup_timeout_seconds"),
270
+ DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS,
271
+ ),
272
+ client_session_timeout_seconds=_as_float(
273
+ raw.get("client_session_timeout_seconds"),
274
+ DEFAULT_MCP_CLIENT_SESSION_TIMEOUT_SECONDS,
275
+ ),
276
+ cache_tools_list=_as_bool(raw.get("cache_tools_list"), DEFAULT_MCP_CACHE_TOOLS_LIST),
277
+ allow_project_config=_as_bool(raw.get("allow_project_config"), False),
278
+ prefer_mcp_web_search=_as_bool(raw.get("prefer_mcp_web_search"), True),
279
+ web_search=McpWebSearchConfig.from_mapping(_as_mapping(raw.get("web_search"))),
280
+ )
281
+
282
+
215
283
  @dataclass(frozen=True)
216
284
  class UiConfig:
217
285
  theme: str = DEFAULT_UI_THEME
@@ -232,6 +300,7 @@ class Settings:
232
300
  logging: LoggingConfig = field(default_factory=LoggingConfig)
233
301
  notify: NotifyConfig = field(default_factory=NotifyConfig)
234
302
  tools: ToolsConfig = field(default_factory=ToolsConfig)
303
+ mcp: McpConfig = field(default_factory=McpConfig)
235
304
  ui: UiConfig = field(default_factory=UiConfig)
236
305
  path: Path | None = None
237
306
 
@@ -249,6 +318,7 @@ class Settings:
249
318
  logging=LoggingConfig.from_mapping(_as_mapping(raw.get("logging"))),
250
319
  notify=NotifyConfig.from_mapping(_as_mapping(raw.get("notify"))),
251
320
  tools=ToolsConfig.from_mapping(_as_mapping(raw.get("tools"))),
321
+ mcp=McpConfig.from_mapping(_as_mapping(raw.get("mcp"))),
252
322
  ui=UiConfig.from_mapping(_as_mapping(raw.get("ui"))),
253
323
  path=path,
254
324
  )
@@ -354,6 +424,21 @@ def write_config(
354
424
  "searxng_url": DEFAULT_WEB_SEARCH_SEARXNG_URL,
355
425
  },
356
426
  },
427
+ "mcp": {
428
+ "enabled": DEFAULT_MCP_ENABLED,
429
+ "connect_timeout_seconds": DEFAULT_MCP_CONNECT_TIMEOUT_SECONDS,
430
+ "cleanup_timeout_seconds": DEFAULT_MCP_CLEANUP_TIMEOUT_SECONDS,
431
+ "client_session_timeout_seconds": DEFAULT_MCP_CLIENT_SESSION_TIMEOUT_SECONDS,
432
+ "cache_tools_list": DEFAULT_MCP_CACHE_TOOLS_LIST,
433
+ "allow_project_config": False,
434
+ "prefer_mcp_web_search": True,
435
+ "web_search": {
436
+ "prefer_mcp": True,
437
+ "preferred_server": "",
438
+ "preferred_tools": [],
439
+ "fallback_to_builtin": True,
440
+ },
441
+ },
357
442
  "ui": {
358
443
  "theme": theme,
359
444
  },
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: skill-creator
3
+ description: Create or update Agent Skills for Deepy. Use when the user wants to design, write, validate, or improve a reusable skill.
4
+ metadata:
5
+ short-description: Create or update an Agent Skill
6
+ ---
7
+
8
+ # Skill Creator
9
+
10
+ Use this skill when creating or improving an Agent Skill.
11
+
12
+ ## Skill Shape
13
+
14
+ An Agent Skill is a directory with a required `SKILL.md` file:
15
+
16
+ ```text
17
+ skill-name/
18
+ ├── SKILL.md
19
+ ├── scripts/
20
+ ├── references/
21
+ └── assets/
22
+ ```
23
+
24
+ `SKILL.md` uses YAML frontmatter followed by concise Markdown instructions:
25
+
26
+ ```markdown
27
+ ---
28
+ name: code-review
29
+ description: Review code changes for correctness, regressions, and missing tests.
30
+ ---
31
+
32
+ # Code Review
33
+
34
+ Review the change before summarizing it. Lead with findings.
35
+ ```
36
+
37
+ ## Rules
38
+
39
+ - Store user skills in `~/.agents/skills/<name>/SKILL.md`.
40
+ - Store project skills in `<project>/.agents/skills/<name>/SKILL.md`.
41
+ - Use lowercase letters, digits, and hyphens for skill names.
42
+ - Keep `SKILL.md` focused. Move detailed material to `references/`.
43
+ - Use `scripts/` for deterministic repeated operations.
44
+ - Use `assets/` for templates or files that support final outputs.
45
+ - Do not add market-specific metadata to the skill directory.
46
+
47
+ ## Creation Process
48
+
49
+ 1. Identify the concrete situations that should trigger the skill.
50
+ 2. Choose a short hyphen-case name.
51
+ 3. Write a clear `description` that says when to use the skill.
52
+ 4. Keep the main workflow in `SKILL.md`.
53
+ 5. Add references, scripts, or assets only when they remove real repetition or ambiguity.
54
+ 6. Validate that the skill can be discovered by Deepy with `/skills list`.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: skill-installer
3
+ description: Install, uninstall, update, and inspect Deepy skills from the configured Deepy skill market.
4
+ metadata:
5
+ short-description: Install skills from Deepy's market
6
+ ---
7
+
8
+ # Skill Installer
9
+
10
+ Use this skill when the user wants to browse, install, uninstall, update, or inspect skills.
11
+
12
+ ## Deepy Skill Commands
13
+
14
+ - `/skills` shows available management commands.
15
+ - `/skills list` lists local project, user, and built-in skills.
16
+ - `/skills search [query]` searches the configured Deepy skill market.
17
+ - `/skills install <name>` installs a market skill into `~/.agents/skills/<name>`.
18
+ - `/skills uninstall <name>` removes a skill installed by Deepy's market installer.
19
+ - `/skills installed` lists market-installed skills.
20
+ - `/skills update <name>` updates one market-installed skill when a newer upload exists.
21
+ - `/skills update --all` updates all market-installed skills when newer uploads exist.
22
+ - `/skill:<name> [request]` actively invokes an installed or built-in skill.
23
+
24
+ ## Storage Rules
25
+
26
+ - Skill content must stay in standard Agent Skills directories.
27
+ - User-installed skills go under `~/.agents/skills`.
28
+ - Project skills go under `.agents/skills`.
29
+ - Deepy market installation records live under `~/.deepy/skill-market/`.
30
+ - Do not put market lock files inside a skill directory.
31
+
32
+ ## Safety
33
+
34
+ Before uninstalling or updating a market-installed skill, compare the current skill content hash with Deepy's install record. If the user modified the skill locally, report the modification and do not overwrite or delete it by default.
@@ -10,6 +10,11 @@ SearXNG instances with limiter enabled are less likely to reject the request as
10
10
  bot traffic. If SearXNG cannot be reached or returns no parseable results, falls
11
11
  back to Deepy's built-in DuckDuckGo HTML search implementation.
12
12
 
13
+ If MCP web-search tools are listed in the system prompt, prefer those MCP tools
14
+ first for web/current-information searches. Use this built-in WebSearch as the
15
+ fallback when MCP search is unavailable, fails, or the user explicitly asks for
16
+ Deepy's built-in search.
17
+
13
18
  Keep searches targeted. After several successful searches, stop searching and
14
19
  synthesize from the gathered sources. Use `WebFetch` for exact URLs that need
15
20
  deeper reading instead of continuing broad search queries.
@@ -18,14 +18,26 @@ def build_deepy_agent(
18
18
  project_root: Path,
19
19
  provider: ProviderBundle | None = None,
20
20
  loaded_skills: list[SkillInfo] | None = None,
21
+ mcp_servers: list[object] | None = None,
22
+ preferred_mcp_web_search_tools: list[str] | None = None,
21
23
  ):
22
24
  from agents import Agent
23
25
 
24
26
  provider = provider or build_provider_bundle(settings)
25
27
  return Agent(
26
28
  name="Deepy",
27
- instructions=build_system_prompt(project_root, settings, loaded_skills=loaded_skills),
29
+ instructions=build_system_prompt(
30
+ project_root,
31
+ settings,
32
+ loaded_skills=loaded_skills,
33
+ preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
34
+ ),
28
35
  model=provider.model,
29
36
  model_settings=provider.model_settings,
30
- tools=build_function_tools(runtime),
37
+ tools=build_function_tools(
38
+ runtime,
39
+ preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
40
+ ),
41
+ mcp_servers=list(mcp_servers or []),
42
+ mcp_config={"include_server_in_tool_names": True},
31
43
  )
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from typing import Any, Literal
5
5
 
6
+ from deepy.utils import json as json_utils
7
+
6
8
  StreamKind = Literal[
7
9
  "text_delta",
8
10
  "reasoning_delta",
@@ -84,7 +86,7 @@ def normalize_stream_event(event: Any) -> DeepyStreamEvent | None:
84
86
  return DeepyStreamEvent(
85
87
  kind="tool_output",
86
88
  name=_tool_name(item),
87
- text=output if isinstance(output, str) else str(output),
89
+ text=_tool_output_text(output),
88
90
  payload={"call_id": _call_id(item)},
89
91
  )
90
92
  if name == "message_output_created":
@@ -159,6 +161,14 @@ def _tool_arguments(item: Any) -> str:
159
161
  return value if isinstance(value, str) else ""
160
162
 
161
163
 
164
+ def _tool_output_text(output: Any) -> str:
165
+ if isinstance(output, str):
166
+ return output
167
+ if output is None:
168
+ return ""
169
+ return json_utils.dumps(output)
170
+
171
+
162
172
  def _call_id(item: Any) -> str:
163
173
  if item is None:
164
174
  return ""