deepy-cli 0.1.14__tar.gz → 0.2.1__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 (84) hide show
  1. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/PKG-INFO +77 -8
  2. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/README.md +75 -7
  3. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/pyproject.toml +3 -9
  4. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/__init__.py +1 -1
  5. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/cli.py +3 -3
  6. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/config/__init__.py +6 -0
  7. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/config/settings.py +85 -0
  8. deepy_cli-0.2.1/src/deepy/data/skills/skill-creator/SKILL.md +54 -0
  9. deepy_cli-0.2.1/src/deepy/data/skills/skill-installer/SKILL.md +34 -0
  10. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/WebSearch.md +5 -0
  11. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/agent.py +18 -2
  12. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/context.py +8 -8
  13. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/events.py +11 -1
  14. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/provider.py +3 -2
  15. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/replay.py +2 -2
  16. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/runner.py +73 -20
  17. deepy_cli-0.2.1/src/deepy/mcp.py +566 -0
  18. deepy_cli-0.2.1/src/deepy/prompts/init_agents.py +36 -0
  19. deepy_cli-0.2.1/src/deepy/prompts/rules.py +139 -0
  20. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/prompts/system.py +43 -3
  21. deepy_cli-0.2.1/src/deepy/skill_market.py +350 -0
  22. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/skills.py +26 -8
  23. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/status.py +9 -0
  24. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/tools/agents.py +51 -7
  25. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/tools/builtin.py +75 -42
  26. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/tools/result.py +1 -1
  27. deepy_cli-0.2.1/src/deepy/types/__init__.py +2 -0
  28. deepy_cli-0.2.1/src/deepy/types/sdk.py +12 -0
  29. deepy_cli-0.2.1/src/deepy/types/tool_payloads.py +15 -0
  30. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/exit_summary.py +2 -1
  31. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/local_command.py +9 -3
  32. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/message_view.py +140 -15
  33. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/prompt_input.py +7 -6
  34. deepy_cli-0.2.1/src/deepy/ui/skill_picker.py +582 -0
  35. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/slash_commands.py +13 -7
  36. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/terminal.py +613 -137
  37. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/welcome.py +3 -2
  38. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/utils/json.py +5 -2
  39. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/utils/notify.py +2 -2
  40. deepy_cli-0.1.14/src/deepy/prompts/rules.py +0 -24
  41. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/__main__.py +0 -0
  42. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/__init__.py +0 -0
  43. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  44. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/WebFetch.md +0 -0
  45. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/__init__.py +0 -0
  46. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/edit.md +0 -0
  47. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/modify.md +0 -0
  48. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/read.md +0 -0
  49. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/shell.md +0 -0
  50. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/data/tools/write.md +0 -0
  51. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/errors.py +0 -0
  52. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/__init__.py +0 -0
  53. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/compaction.py +0 -0
  54. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/model_capabilities.py +0 -0
  55. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/llm/thinking.py +0 -0
  56. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/prompts/__init__.py +0 -0
  57. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/prompts/compact.py +0 -0
  58. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/prompts/runtime_context.py +0 -0
  59. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/prompts/tool_docs.py +0 -0
  60. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/sessions/__init__.py +0 -0
  61. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/sessions/jsonl.py +0 -0
  62. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/sessions/manager.py +0 -0
  63. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/tools/__init__.py +0 -0
  64. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/tools/file_state.py +0 -0
  65. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/tools/shell_output.py +0 -0
  66. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/tools/shell_utils.py +0 -0
  67. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/__init__.py +0 -0
  68. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/app.py +0 -0
  69. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/ask_user_question.py +0 -0
  70. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/file_mentions.py +0 -0
  71. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/loading_text.py +0 -0
  72. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/markdown.py +0 -0
  73. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/model_picker.py +0 -0
  74. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/prompt_buffer.py +0 -0
  75. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/session_list.py +0 -0
  76. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/session_picker.py +0 -0
  77. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/styles.py +0 -0
  78. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/theme_picker.py +0 -0
  79. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/ui/thinking_state.py +0 -0
  80. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/update_check.py +0 -0
  81. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/usage.py +0 -0
  82. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/utils/__init__.py +0 -0
  83. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/utils/debug_logger.py +0 -0
  84. {deepy_cli-0.1.14 → deepy_cli-0.2.1}/src/deepy/utils/error_logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: deepy-cli
3
- Version: 0.1.14
3
+ Version: 0.2.1
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 ty check src`
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
 
@@ -300,7 +369,7 @@ domain-specific workflows without changing global config.
300
369
  uv sync --group dev
301
370
  uv run pytest
302
371
  uv run ruff check
303
- uv run pyright
372
+ uv run ty check src
304
373
  uv build
305
374
  ```
306
375
 
@@ -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 ty check src`
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
 
@@ -272,7 +340,7 @@ domain-specific workflows without changing global config.
272
340
  uv sync --group dev
273
341
  uv run pytest
274
342
  uv run ruff check
275
- uv run pyright
343
+ uv run ty check src
276
344
  uv build
277
345
  ```
278
346
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.1.14"
3
+ version = "0.2.1"
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",
@@ -38,10 +39,10 @@ Issues = "https://github.com/kirineko/deepy/issues"
38
39
 
39
40
  [dependency-groups]
40
41
  dev = [
41
- "pyright>=1.1.407",
42
42
  "pytest>=8.0",
43
43
  "pytest-asyncio>=0.24",
44
44
  "ruff>=0.14",
45
+ "ty>=0.0.32",
45
46
  ]
46
47
 
47
48
  [tool.pytest.ini_options]
@@ -53,13 +54,6 @@ target-version = "py312"
53
54
  src = ["src", "tests"]
54
55
  exclude = ["dist", "reference", "spec"]
55
56
 
56
- [tool.pyright]
57
- pythonVersion = "3.12"
58
- typeCheckingMode = "off"
59
- include = ["src", "tests"]
60
- exclude = ["dist", "reference", "spec"]
61
- reportMissingTypeStubs = false
62
-
63
57
  [tool.uv.build-backend]
64
58
  module-name = "deepy"
65
59
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.1.14"
3
+ __version__ = "0.2.1"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -4,7 +4,7 @@ import argparse
4
4
  import asyncio
5
5
  import sys
6
6
  from pathlib import Path
7
- from typing import Sequence
7
+ from typing import Any, Sequence
8
8
 
9
9
  import tomli_w
10
10
 
@@ -198,7 +198,7 @@ def _cmd_config_theme(args: argparse.Namespace) -> int:
198
198
  return 0
199
199
 
200
200
 
201
- def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, object]]:
201
+ def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, Any]]:
202
202
  settings = load_settings(args.config)
203
203
  checks: list[dict[str, object]] = []
204
204
 
@@ -251,7 +251,7 @@ def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, object]]:
251
251
  }
252
252
 
253
253
 
254
- async def _doctor_live(settings: Settings) -> dict[str, object]:
254
+ async def _doctor_live(settings: Settings) -> dict[str, Any]:
255
255
  from agents import Agent, RunConfig, Runner
256
256
 
257
257
  provider = build_provider_bundle(settings)
@@ -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.
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  from deepy.config import Settings
6
7
  from deepy.prompts import build_system_prompt
@@ -10,6 +11,9 @@ from deepy.tools.agents import build_function_tools
10
11
 
11
12
  from .provider import ProviderBundle, build_provider_bundle
12
13
 
14
+ if TYPE_CHECKING:
15
+ from agents.mcp import MCPServer
16
+
13
17
 
14
18
  def build_deepy_agent(
15
19
  settings: Settings,
@@ -18,14 +22,26 @@ def build_deepy_agent(
18
22
  project_root: Path,
19
23
  provider: ProviderBundle | None = None,
20
24
  loaded_skills: list[SkillInfo] | None = None,
25
+ mcp_servers: list[MCPServer] | None = None,
26
+ preferred_mcp_web_search_tools: list[str] | None = None,
21
27
  ):
22
28
  from agents import Agent
23
29
 
24
30
  provider = provider or build_provider_bundle(settings)
25
31
  return Agent(
26
32
  name="Deepy",
27
- instructions=build_system_prompt(project_root, settings, loaded_skills=loaded_skills),
33
+ instructions=build_system_prompt(
34
+ project_root,
35
+ settings,
36
+ loaded_skills=loaded_skills,
37
+ preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
38
+ ),
28
39
  model=provider.model,
29
40
  model_settings=provider.model_settings,
30
- tools=build_function_tools(runtime),
41
+ tools=build_function_tools(
42
+ runtime,
43
+ preferred_mcp_web_search_tools=preferred_mcp_web_search_tools,
44
+ ),
45
+ mcp_servers=list(mcp_servers or []),
46
+ mcp_config={"include_server_in_tool_names": True},
31
47
  )
@@ -1,16 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable
4
3
  from math import ceil
5
4
  from typing import Any
6
5
 
7
6
  from deepy.config import Settings
7
+ from deepy.types.sdk import SessionInputCallback
8
8
  from deepy.utils import json as json_utils
9
9
 
10
+ tiktoken: Any | None
10
11
  try:
11
- import tiktoken
12
+ import tiktoken as _tiktoken
12
13
  except Exception: # pragma: no cover - optional dependency fallback.
13
- tiktoken = None # type: ignore[assignment]
14
+ tiktoken = None
15
+ else:
16
+ tiktoken = _tiktoken
14
17
 
15
18
  _ENCODING = None
16
19
 
@@ -38,11 +41,8 @@ def estimate_tokens_for_items(items: list[dict[str, Any]]) -> int:
38
41
  return sum(estimate_tokens_for_item(item) for item in items)
39
42
 
40
43
 
41
- def build_session_input_callback(settings: Settings) -> Callable[
42
- [list[dict[str, Any]], list[dict[str, Any]]],
43
- list[dict[str, Any]],
44
- ]:
45
- def callback(history: list[dict[str, Any]], new_input: list[dict[str, Any]]) -> list[dict[str, Any]]:
44
+ def build_session_input_callback(settings: Settings) -> SessionInputCallback:
45
+ def callback(history: list[Any], new_input: list[Any]) -> list[Any]:
46
46
  return [*history, *new_input]
47
47
 
48
48
  return callback
@@ -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 ""