shared_tools 0.4.4 → 0.6.0

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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +211 -7
  4. data/lib/shared_tools/apply_patch_tool.rb +4 -0
  5. data/lib/shared_tools/bash_tool.rb +4 -0
  6. data/lib/shared_tools/bundle_tool.rb +4 -0
  7. data/lib/shared_tools/command_guard.rb +35 -0
  8. data/lib/shared_tools/csv_read_tool.rb +4 -0
  9. data/lib/shared_tools/csv_write_tool.rb +4 -0
  10. data/lib/shared_tools/data_path.rb +52 -0
  11. data/lib/shared_tools/diff_tool.rb +4 -0
  12. data/lib/shared_tools/download_file_tool.rb +4 -0
  13. data/lib/shared_tools/gem_tool.rb +4 -0
  14. data/lib/shared_tools/git_tool.rb +5 -0
  15. data/lib/shared_tools/glob_tool.rb +4 -0
  16. data/lib/shared_tools/http_request_tool.rb +4 -0
  17. data/lib/shared_tools/json_query_tool.rb +4 -0
  18. data/lib/shared_tools/lint_tool.rb +4 -0
  19. data/lib/shared_tools/mcp/brave_search_client.rb +18 -16
  20. data/lib/shared_tools/mcp/chart_client.rb +18 -15
  21. data/lib/shared_tools/mcp/client_support.rb +103 -0
  22. data/lib/shared_tools/mcp/github_client.rb +23 -19
  23. data/lib/shared_tools/mcp/hugging_face_client.rb +22 -22
  24. data/lib/shared_tools/mcp/memory_client.rb +20 -15
  25. data/lib/shared_tools/mcp/notion_client.rb +21 -19
  26. data/lib/shared_tools/mcp/playwright_client.rb +19 -15
  27. data/lib/shared_tools/mcp/sequential_thinking_client.rb +17 -14
  28. data/lib/shared_tools/mcp/slack_client.rb +30 -25
  29. data/lib/shared_tools/mcp/tavily_client.rb +21 -21
  30. data/lib/shared_tools/mcp.rb +32 -41
  31. data/lib/shared_tools/multi_edit_tool.rb +4 -0
  32. data/lib/shared_tools/parse_ruby_tool.rb +4 -0
  33. data/lib/shared_tools/process_kill_tool.rb +4 -0
  34. data/lib/shared_tools/process_list_tool.rb +4 -0
  35. data/lib/shared_tools/process_output_tool.rb +4 -0
  36. data/lib/shared_tools/process_registry.rb +224 -0
  37. data/lib/shared_tools/process_runner.rb +69 -0
  38. data/lib/shared_tools/process_start_tool.rb +4 -0
  39. data/lib/shared_tools/python_tests_tool.rb +4 -0
  40. data/lib/shared_tools/replace_in_files_tool.rb +4 -0
  41. data/lib/shared_tools/ruby_outline.rb +207 -0
  42. data/lib/shared_tools/run_tests_tool.rb +4 -0
  43. data/lib/shared_tools/text_diff.rb +80 -0
  44. data/lib/shared_tools/todo_write_tool.rb +4 -0
  45. data/lib/shared_tools/toml_parser.rb +409 -0
  46. data/lib/shared_tools/toml_query_tool.rb +4 -0
  47. data/lib/shared_tools/tools/apply_patch_tool.rb +122 -0
  48. data/lib/shared_tools/tools/bash_tool.rb +102 -0
  49. data/lib/shared_tools/tools/browser/click_tool.rb +1 -1
  50. data/lib/shared_tools/tools/browser/inspect_tool.rb +1 -1
  51. data/lib/shared_tools/tools/browser/page_inspect_tool.rb +1 -1
  52. data/lib/shared_tools/tools/browser/page_screenshot_tool.rb +1 -1
  53. data/lib/shared_tools/tools/browser/selector_inspect_tool.rb +1 -1
  54. data/lib/shared_tools/tools/browser/text_field_area_set_tool.rb +1 -1
  55. data/lib/shared_tools/tools/browser/visit_tool.rb +1 -1
  56. data/lib/shared_tools/tools/browser_tool.rb +1 -1
  57. data/lib/shared_tools/tools/bundle_tool.rb +96 -0
  58. data/lib/shared_tools/tools/calculator_tool.rb +1 -1
  59. data/lib/shared_tools/tools/clipboard_tool.rb +1 -1
  60. data/lib/shared_tools/tools/composite_analysis_tool.rb +1 -1
  61. data/lib/shared_tools/tools/computer_tool.rb +1 -1
  62. data/lib/shared_tools/tools/cron_tool.rb +1 -1
  63. data/lib/shared_tools/tools/csv_read_tool.rb +82 -0
  64. data/lib/shared_tools/tools/csv_write_tool.rb +67 -0
  65. data/lib/shared_tools/tools/current_date_time_tool.rb +1 -1
  66. data/lib/shared_tools/tools/data_science_kit.rb +1 -1
  67. data/lib/shared_tools/tools/database_query_tool.rb +1 -1
  68. data/lib/shared_tools/tools/database_tool.rb +1 -1
  69. data/lib/shared_tools/tools/diff_tool.rb +48 -0
  70. data/lib/shared_tools/tools/disk/directory_create_tool.rb +1 -1
  71. data/lib/shared_tools/tools/disk/directory_delete_tool.rb +1 -1
  72. data/lib/shared_tools/tools/disk/directory_list_tool.rb +1 -1
  73. data/lib/shared_tools/tools/disk/directory_move_tool.rb +1 -1
  74. data/lib/shared_tools/tools/disk/file_create_tool.rb +1 -1
  75. data/lib/shared_tools/tools/disk/file_delete_tool.rb +1 -1
  76. data/lib/shared_tools/tools/disk/file_move_tool.rb +1 -1
  77. data/lib/shared_tools/tools/disk/file_read_tool.rb +1 -1
  78. data/lib/shared_tools/tools/disk/file_replace_tool.rb +1 -1
  79. data/lib/shared_tools/tools/disk/file_write_tool.rb +1 -1
  80. data/lib/shared_tools/tools/disk_tool.rb +1 -1
  81. data/lib/shared_tools/tools/dns_tool.rb +1 -1
  82. data/lib/shared_tools/tools/doc/docx_reader_tool.rb +1 -1
  83. data/lib/shared_tools/tools/doc/pdf_reader_tool.rb +1 -1
  84. data/lib/shared_tools/tools/doc/spreadsheet_reader_tool.rb +1 -1
  85. data/lib/shared_tools/tools/doc/text_reader_tool.rb +1 -1
  86. data/lib/shared_tools/tools/doc_tool.rb +1 -1
  87. data/lib/shared_tools/tools/docker/compose_run_tool.rb +1 -1
  88. data/lib/shared_tools/tools/download_file_tool.rb +87 -0
  89. data/lib/shared_tools/tools/enabler.rb +2 -2
  90. data/lib/shared_tools/tools/error_handling_tool.rb +1 -1
  91. data/lib/shared_tools/tools/eval/python_eval_tool.rb +1 -1
  92. data/lib/shared_tools/tools/eval/ruby_eval_tool.rb +1 -1
  93. data/lib/shared_tools/tools/eval/shell_eval_tool.rb +1 -1
  94. data/lib/shared_tools/tools/eval_tool.rb +1 -1
  95. data/lib/shared_tools/tools/gem_tool.rb +157 -0
  96. data/lib/shared_tools/tools/git/add_tool.rb +73 -0
  97. data/lib/shared_tools/tools/git/blame_tool.rb +77 -0
  98. data/lib/shared_tools/tools/git/branch_tool.rb +55 -0
  99. data/lib/shared_tools/tools/git/checkout_tool.rb +69 -0
  100. data/lib/shared_tools/tools/git/commit_tool.rb +73 -0
  101. data/lib/shared_tools/tools/git/diff_tool.rb +69 -0
  102. data/lib/shared_tools/tools/git/grep_tool.rb +82 -0
  103. data/lib/shared_tools/tools/git/helpers.rb +70 -0
  104. data/lib/shared_tools/tools/git/log_tool.rb +71 -0
  105. data/lib/shared_tools/tools/git/show_tool.rb +66 -0
  106. data/lib/shared_tools/tools/git/status_tool.rb +46 -0
  107. data/lib/shared_tools/tools/git.rb +18 -0
  108. data/lib/shared_tools/tools/git_tool.rb +148 -0
  109. data/lib/shared_tools/tools/glob_tool.rb +75 -0
  110. data/lib/shared_tools/tools/http_helpers.rb +119 -0
  111. data/lib/shared_tools/tools/http_request_tool.rb +91 -0
  112. data/lib/shared_tools/tools/json_query_tool.rb +81 -0
  113. data/lib/shared_tools/tools/lint_tool.rb +93 -0
  114. data/lib/shared_tools/tools/multi_edit_tool.rb +134 -0
  115. data/lib/shared_tools/tools/notification_tool.rb +1 -1
  116. data/lib/shared_tools/tools/parse_ruby_tool.rb +127 -0
  117. data/lib/shared_tools/tools/process_kill_tool.rb +55 -0
  118. data/lib/shared_tools/tools/process_list_tool.rb +41 -0
  119. data/lib/shared_tools/tools/process_output_tool.rb +69 -0
  120. data/lib/shared_tools/tools/process_start_tool.rb +0 -0
  121. data/lib/shared_tools/tools/python_tests_tool.rb +105 -0
  122. data/lib/shared_tools/tools/replace_in_files_tool.rb +166 -0
  123. data/lib/shared_tools/tools/run_tests_tool.rb +108 -0
  124. data/lib/shared_tools/tools/search_codebase_tool.rb +178 -47
  125. data/lib/shared_tools/tools/secure_tool_template.rb +1 -1
  126. data/lib/shared_tools/tools/system_info_tool.rb +1 -1
  127. data/lib/shared_tools/tools/todo_write_tool.rb +81 -0
  128. data/lib/shared_tools/tools/toml_query_tool.rb +81 -0
  129. data/lib/shared_tools/tools/toolchain_helpers.rb +64 -0
  130. data/lib/shared_tools/tools/tree_tool.rb +100 -0
  131. data/lib/shared_tools/tools/weather_tool.rb +1 -1
  132. data/lib/shared_tools/tools/web_fetch_tool.rb +85 -0
  133. data/lib/shared_tools/tools/workflow_manager_tool.rb +1 -1
  134. data/lib/shared_tools/tools/yaml_query_tool.rb +86 -0
  135. data/lib/shared_tools/tree_tool.rb +4 -0
  136. data/lib/shared_tools/url_guard.rb +106 -0
  137. data/lib/shared_tools/utilities.rb +2 -2
  138. data/lib/shared_tools/version.rb +1 -1
  139. data/lib/shared_tools/web_fetch_tool.rb +4 -0
  140. data/lib/shared_tools/yaml_query_tool.rb +4 -0
  141. data/lib/shared_tools.rb +1 -1
  142. metadata +516 -409
  143. data/lib/shared_tools/mcp/streamable_http_patch.rb +0 -42
  144. data/lib/shared_tools/tools/version.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 679fe640e745e716a1ec2c9cb77fd9af47b67b09ddd5caba23ef9b6fbbf50d24
4
- data.tar.gz: afa308531e5afc205b244771db0ed2e8965e5532d5f642d81c3284819db6f7b6
3
+ metadata.gz: 28d0b117ea90e414478dd4e16006c38ca33fd1bcd4a7778ac5a1aed87f168dbd
4
+ data.tar.gz: bba1e30acb8b8e329edf85460d87ded7838ce4a687560dca7542d0c6a4ad7a49
5
5
  SHA512:
6
- metadata.gz: 87264d8b31d6b016e56ac361fa528a73071163f4dfaa4d562e877e20b7d011aa85a0de033212aa326fe9e8983940893d046237eab043c5ee6f61306dc126c21b
7
- data.tar.gz: eee3b6364a669539ff7d1ffb290e1255416c0a20f32f68873a4d69061dcf41e5675a23b2ecb3fcacaf7714f6ad0b69d726cf3577642bceef611895574870d44c
6
+ metadata.gz: 02c7930f52bc3498f9f4e7d2648fb10a020a908688e3e551f3ab44857593a4730f2eb531e8abd4924d24c777a286266076a17624c83d42266b0aa19f8e123c61
7
+ data.tar.gz: 1341ca665381c7969fc30c4ff650531c0d1cc088a63523c25e3f9991ab2d2b5e24837ef9caed8efbe79959d8d9ea9f666abc4997b469f3de2fb113d4af6e9aba
data/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ #### ruby_llm 2.1 migration (breaking)
6
+ - **Breaking:** Requires `ruby_llm >= 2.1.1, < 3`. Under ruby_llm 2.x every tool that used the 1.x `params do ... end` macro raised `NoMethodError` inside its class body; the constant already existed, so a half-built class loaded without `#execute`, and calling the tool failed with `NotImplementedError: Subclasses must implement #execute`. All 81 tool files now use `parameters do ... end` (same block DSL, now provided by `schematist`), and `Tools::Enabler`'s `param :tool, desc:` is now `parameter :tool, description:`. The generated argument schemas are unchanged apart from the top-level `"strict": true` marker that ruby_llm 1.x added and 2.x does not. Read a tool's schema with `tool.parameters_schema` (was `params_schema`).
7
+ - **Breaking:** Dropped the `ruby_llm-mcp` dependency; it cannot be loaded alongside ruby_llm 2.1's built-in `RubyLLM::MCP`, which has no `add_client` / `clients` registry. Each MCP client in `lib/shared_tools/mcp/` is now a `RubyLLM::MCP` subclass that describes one server with the class DSL (`command`, `env`, `url`, `bearer_token`, `timeout`): `SharedTools::MCP::GitHub`, `Notion`, `Slack`, `HuggingFace`, `Tavily`, `Memory`, `SequentialThinking`, `Chart`, `BraveSearch`, `Playwright`. Pass the class to a chat or robot (`RubyLLM.chat.with_mcp(SharedTools::MCP::GitHub)`, `RobotLab.build(mcp: [SharedTools::MCP::GitHub])`) instead of looking it up with `RubyLLM::MCP.clients["github"]`.
8
+ - **Breaking:** Requiring a client file no longer has side effects. It does not check environment variables, does not run `brew`/`apt`/`dnf`, and does not connect; environment variables are read when the server process starts. Use `Klass.available?` / `missing_envars` / `verify!` (raises `LoadError` like before) and `Klass.install_packages!`. `require 'shared_tools/mcp'` loads every definition, records availability for `SharedTools.mcp_status`, and `SharedTools::MCP.available` lists the usable classes.
9
+ - **Breaking:** MCP server names are now symbol-friendly: `brave-search` → `brave_search`, `hugging-face` → `hugging_face`, `sequential-thinking` → `sequential_thinking` (others unchanged). These are the names in `SharedTools.mcp_status` and `chat.mcp[...]`.
10
+ - Removed `lib/shared_tools/mcp/streamable_http_patch.rb`; it patched a `ruby_llm-mcp` internal. ruby_llm 2.1's spec-compliant SSE parser already handles mixed CRLF/LF line endings.
11
+ - Added `test/shared_tools/tool_classes_test.rb`: every shipped `RubyLLM::Tool` subclass must define its own `#execute`, expose an object `parameters_schema` whose properties `#execute` accepts, and no 1.x `params do` / `param :` may remain in `lib/`. It also calls `current_date_time`, `system_info` and `dns` (used by aia's demo 18) through `Tool#call` and checks the data they return. Added `test/shared_tools/mcp/clients_test.rb` (class-level only; no servers are started).
12
+ - Updated the MCP examples, `lib/shared_tools/mcp/README.md`, `README.md` and `docs/` for the new MCP classes.
13
+
14
+ #### Tools
15
+ - Added Git tools, ported from `ruby_llm-toolbox`: `SharedTools::Tools::Git::StatusTool`, `DiffTool`, `LogTool`, `ShowTool`, `BlameTool`, `BranchTool`, and `GrepTool` (read-only), plus `AddTool`, `CommitTool`, and `CheckoutTool` (mutating, require `SharedTools.execute?` authorization). All shell out to `git` with `core.fsmonitor` disabled and `--no-ext-diff --no-textconv`, so an untrusted checkout can't turn a read into command execution; refs are validated against option injection and paths are confined to `repo_root`. Also added `SharedTools::Tools::GitTool`, a facade dispatching on `action`.
16
+ - Added `SharedTools::Tools::JsonQueryTool`, `YamlQueryTool`, and `TomlQueryTool` — query JSON/YAML/TOML (from a file or inline string) with a dot/bracket path expression (`users[0].name`, `services[].image`), or pretty-print with no query. YAML uses `safe_load`; TOML is parsed with a new dependency-free `SharedTools::TomlParser`, also ported from `ruby_llm-toolbox`. Path traversal is shared via a new `SharedTools::DataPath` module.
17
+ - Added `SharedTools::Tools::CsvReadTool` (read-only) and `SharedTools::Tools::CsvWriteTool` (mutating, requires authorization).
18
+ - Added `SharedTools::Tools::DiffTool`, ported from `ruby_llm-toolbox` — compares two in-memory blocks of text and returns a readable unified diff, via a new dependency-free `SharedTools::TextDiff` module. Read-only, no filesystem access.
19
+ - Added `SharedTools::Tools::MultiEditTool` — applies several find/replace edits to one file atomically; each edit's `old_string` must match exactly once (or `replace_all`) or the whole batch is rejected and nothing is written. Mutating, requires `SharedTools.execute?` authorization.
20
+ - Added `SharedTools::Tools::ApplyPatchTool` — applies a unified diff via `git apply`, validated with `--check` first; every touched path is confined to `root`. Set `check: true` for a dry run. Mutating, requires authorization.
21
+ - Added `SharedTools::Tools::GlobTool`, ported from `ruby_llm-toolbox` — finds files matching a glob pattern (`**/*.rb`), returns sorted paths relative to `base`. Read-only, in-process.
22
+ - Added `SharedTools::Tools::TreeTool`, ported from `ruby_llm-toolbox` — renders a depth-limited directory tree, skipping `.git`/`node_modules`/hidden entries by default. Read-only, in-process.
23
+ - **Breaking:** Replaced `SharedTools::Tools::SearchCodebaseTool`'s implementation with a pure-Ruby scanner ported from `ruby_llm-toolbox`'s `grep_files`, dropping the `rg`/`grep` shell-out dependency entirely. The regex is compiled with a per-match timeout so a malicious pattern can't hang the process (ReDoS guard), and `context`/`before`/`after` params add grep-style context lines around matches. **Param changes:** `term:` → `pattern:` (already treated as a regex by `rg`/`grep`, now explicit and validated), `extension:` → `glob:` (a filename glob like `"*.rb"` instead of a bare extension like `"rb"`). `max_results:` is unchanged. `result[:tool]` now always reports `"ruby"` instead of `"rg"`/`"grep"`.
24
+ - Added `SharedTools::Tools::ReplaceInFilesTool` — batch find/replace across files matching a glob (literal or regex, ReDoS-guarded), with `dry_run` preview. Mutating, requires authorization.
25
+ - Added Ruby dev tools, ported from `ruby_llm-toolbox`: `SharedTools::Tools::GemTool` (read-only RubyGems.org metadata lookup), `BundleTool` (Bundler install/update/outdated/check/lock/add), `LintTool` (RuboCop/Standard, auto-detected), and `ParseRubyTool` (structural outline of a Ruby file via a new `SharedTools::RubyOutline` module — Prism on Ruby 3.3+, Ripper stdlib fallback otherwise). Bundle/Lint require authorization; Gem/ParseRuby are read-only.
26
+ - Added background process management, ported from `ruby_llm-toolbox`: `SharedTools::Tools::ProcessStartTool`, `ProcessListTool`, `ProcessOutputTool`, and `ProcessKillTool`, backed by new `SharedTools::ProcessRegistry`/`ManagedProcess` support (thread-safe registry, bounded output buffers, SIGTERM→SIGKILL escalation across the whole process group, all processes cleaned up at exit). `ProcessStartTool` requires an executable on `allowed_commands` (empty by default) via a new `SharedTools::CommandGuard`, and requires authorization. `ProcessKillTool` requires no authorization — always available so a runaway can be stopped.
27
+ - Added guarded network tools, ported from `ruby_llm-toolbox`: `SharedTools::Tools::WebFetchTool` (fetch a URL, HTML stripped to text; read-only), `HttpRequestTool` (general HTTP client; GET/HEAD read-only, POST/PUT/PATCH/DELETE require authorization), and `DownloadFileTool` (download a URL to a file within `root`; requires authorization). All three go through a new `SharedTools::UrlGuard` (SSRF protection: http/https only, no embedded credentials, blocks private/loopback/link-local/cloud-metadata addresses, and pins the connection to the vetted IP to close the DNS-rebinding window).
28
+ - Added `SharedTools::Tools::TodoWriteTool` — maintains a replace-the-whole-list task tracker for multi-step work. Read-only (in-memory only), no authorization needed.
29
+ - Added `SharedTools::Tools::BashTool` — runs one allowlisted executable with argv-only arguments (no shell, so no pipes/redirects/globs/injection surface). `allowed_commands` is empty by default. Requires authorization.
30
+ - Added `SharedTools::Tools::RunTestsTool` (RSpec/Minitest, auto-detected) and `SharedTools::Tools::PythonTestsTool` (pytest/unittest), both via a new `SharedTools::Tools::ToolchainHelpers` module shared with BundleTool/LintTool. Both require authorization.
31
+ - **Scope note:** `ruby_llm-toolbox`'s `run_rust`/`run_python`/`run_ruby` (Docker-sandboxed code execution) were intentionally not ported — they depend on a much larger Docker sandbox subsystem this gem doesn't have. Also dropped: the toolbox's `allow_unsafe`/operator-override escape hatches on `bash_tool`, `process_start`, `http_request`, and `web_fetch` — shared_tools has no equivalent global-override concept, so commands/URLs are simply allowed or refused.
32
+
33
+ #### Tests
34
+ - Added `test/shared_tools/tools/git/*_tool_test.rb` (10 files) and `test/shared_tools/tools/git_tool_test.rb` covering all git tools, including ref-injection and path-jail rejection.
35
+ - Added `test/shared_tools/tools/{json,yaml,toml,csv}_*_tool_test.rb` covering the structured-data tools.
36
+ - Added `test/shared_tools/tools/{diff,multi_edit,apply_patch,replace_in_files}_tool_test.rb` covering the file-editing tools, including atomicity-on-failure and path-jail rejection.
37
+ - Added `test/shared_tools/tools/{glob,tree}_tool_test.rb` and rewrote `test/shared_tools/tools/search_codebase_tool_test.rb` for the new `pattern:`/`glob:` params, context blocks, and regex-error handling.
38
+ - Added `test/shared_tools/tools/{gem,bundle,lint,parse_ruby}_tool_test.rb` covering the Ruby dev tools.
39
+ - Added `test/shared_tools/tools/process_{start,list,output,kill}_tool_test.rb` covering the process-management tools, including allowlist rejection, the concurrency limit, and full start/list/output/kill lifecycles.
40
+ - Added `test/shared_tools/tools/{web_fetch,http_request,download_file}_tool_test.rb` covering the network tools, including live SSRF-block assertions against loopback and cloud-metadata addresses.
41
+ - Added `test/shared_tools/url_guard_test.rb` and `test/shared_tools/command_guard_test.rb` covering the new security modules directly.
42
+ - Added `test/shared_tools/tools/{todo_write,bash,run_tests,python_tests}_tool_test.rb`.
43
+ - Added `init_git_repo` / `git_commit_all` test helpers to `test/test_helper.rb`.
44
+
5
45
  ### [0.4.4] - 2026-06-06
6
46
 
7
47
  #### Tools
data/README.md CHANGED
@@ -16,7 +16,7 @@ SharedTools is a comprehensive collection of production-ready tools designed for
16
16
 
17
17
  ### Key Features
18
18
 
19
- - 🔧 **21+ Production Tools** — Browser automation, file operations, database queries, code evaluation, document processing, DNS and WHOIS lookups, IP geolocation, data science, weather data, workflow management, system utilities, notifications, and more
19
+ - 🔧 **45+ Production Tools** — Browser automation, file discovery and editing (glob/tree/diff/multi-edit/patch/replace), git, structured data (JSON/YAML/TOML/CSV), Ruby dev tooling (gem/bundle/lint/parse), background process management, guarded HTTP (fetch/request/download), database queries, code evaluation, document processing, DNS and WHOIS lookups, IP geolocation, data science, weather data, workflow management, system utilities, notifications, and more
20
20
  - 🔒 **Human-in-the-Loop Authorization** — Built-in safety system for sensitive operations
21
21
  - 🎯 **Facade Pattern** — Simplified interfaces with complex capabilities under the hood
22
22
  - 🔌 **Pluggable Drivers** — Swap implementations for testing or different backends
@@ -120,6 +120,148 @@ content = disk.execute(action: "file_read", path: "./report.txt")
120
120
 
121
121
  ---
122
122
 
123
+ ### 🔍 File Discovery Tools
124
+
125
+ Find files and content without shelling out to `rg`/`grep`/`find`. Pure Ruby, no external binary dependency.
126
+
127
+ - **`GlobTool`** — finds files matching a glob pattern (`**/*.rb`, `app/models/*.rb`), returns sorted paths relative to `base`.
128
+ - **`TreeTool`** — renders a depth-limited directory tree, skipping `.git`/`node_modules`/hidden entries by default.
129
+ - **`SearchCodebaseTool`** — searches file contents for a regular expression, returning `path:line: text` hits (or context blocks with `context`/`before`/`after`). ReDoS-guarded with a per-match regex timeout, and caps results at `max_results` (default 50, max 500).
130
+
131
+ ```ruby
132
+ glob = SharedTools::Tools::GlobTool.new
133
+ glob.execute(pattern: "**/*.rb", base: "./lib")
134
+
135
+ tree = SharedTools::Tools::TreeTool.new
136
+ tree.execute(path: "./lib", max_depth: 2)
137
+
138
+ search = SharedTools::Tools::SearchCodebaseTool.new
139
+ search.execute(pattern: "def execute", glob: "*.rb")
140
+ search.execute(pattern: "TODO", context: 2) # 2 lines of context around each match
141
+ ```
142
+
143
+ > **Note:** `SearchCodebaseTool`'s implementation changed from shelling out to `rg`/`grep` to a pure-Ruby, ReDoS-guarded scanner. The `term:` and `extension:` params were renamed to `pattern:` (a regex) and `glob:` (a filename glob, e.g. `"*.rb"` instead of `"rb"`); `max_results:` is unchanged. The `tool:` result key now always reports `"ruby"`.
144
+
145
+ ---
146
+
147
+ ### ✂️ File Editing Tools
148
+
149
+ Precise, deterministic text editing that complements `DiskTool` and `GitTool` — an alternative to `Disk::FileReplaceTool`'s single global replace for coding-agent workflows.
150
+
151
+ - **`DiffTool`** — compares two in-memory blocks of text and returns a readable unified diff. Read-only, no filesystem access.
152
+ - **`MultiEditTool`** — applies several find/replace edits to one file atomically. Each edit's `old_string` must match exactly once (add context or set `replace_all`) or the whole batch is rejected and nothing is written. Requires authorization.
153
+ - **`ApplyPatchTool`** — applies a unified diff (as produced by `git diff`, `DiffTool`, or `diff -u`) via `git apply`, validating with `--check` first. Every touched path is confined to `root`. Set `check: true` for a dry run. Requires authorization.
154
+ - **`ReplaceInFilesTool`** — batch find/replace across every file in `root` matching a glob (default `**/*`). Literal by default; set `regex: true` for a pattern with `\1`-style backreferences. ReDoS-guarded, skips binary files and ignored directories. Set `dry_run: true` to preview without writing. Requires authorization (dry runs don't).
155
+
156
+ ```ruby
157
+ diff = SharedTools::Tools::DiffTool.new
158
+ diff.execute(old: old_text, new: new_text)
159
+
160
+ multi_edit = SharedTools::Tools::MultiEditTool.new(root: "./my-project")
161
+ multi_edit.execute(path: "lib/foo.rb", edits: [
162
+ { old_string: "def bar", new_string: "def baz" },
163
+ { old_string: "BAR", new_string: "BAZ", replace_all: true }
164
+ ])
165
+
166
+ apply_patch = SharedTools::Tools::ApplyPatchTool.new(root: "./my-project")
167
+ apply_patch.execute(patch: unified_diff_text, check: true) # dry run
168
+ apply_patch.execute(patch: unified_diff_text)
169
+
170
+ replace = SharedTools::Tools::ReplaceInFilesTool.new(root: "./my-project")
171
+ replace.execute(pattern: "OldClassName", replacement: "NewClassName", glob: "**/*.rb")
172
+ ```
173
+
174
+ ---
175
+
176
+ ### 🔀 Git Tools
177
+
178
+ Read a repository's history and working-tree state, and make basic mutating changes. Read-only actions (`status`, `diff`, `log`, `show`, `blame`, `branch`, `grep`) require no authorization; mutating actions (`add`, `commit`, `checkout`) require user authorization. External diff drivers, textconv filters, and `core.fsmonitor` are disabled so an untrusted checkout can't turn a read into command execution.
179
+
180
+ **Actions:** `status`, `diff`, `log`, `show`, `blame`, `branch`, `grep`, `add`, `commit`, `checkout`
181
+
182
+ ```ruby
183
+ git = SharedTools::Tools::GitTool.new(repo_root: "./my-project")
184
+ git.execute(action: "status")
185
+ git.execute(action: "diff", staged: true)
186
+ git.execute(action: "log", count: 10, path: "lib/foo.rb")
187
+ git.execute(action: "blame", path: "lib/foo.rb", start_line: 10, end_line: 20)
188
+ git.execute(action: "commit", message: "Fix the thing", all: true)
189
+ ```
190
+
191
+ Each action is also available as a standalone tool, e.g. `SharedTools::Tools::Git::StatusTool`, `SharedTools::Tools::Git::DiffTool`, `SharedTools::Tools::Git::CommitTool`.
192
+
193
+ ---
194
+
195
+ ### 💎 Ruby Dev Tools
196
+
197
+ Tooling for working on a Ruby project: gem metadata, Bundler, linting, and structural code navigation.
198
+
199
+ - **`GemTool`** — read-only RubyGems.org metadata lookup (`info`, `version`, `dependencies`, `search`). No authorization needed.
200
+ - **`BundleTool`** — runs Bundler (`install`, `update`, `outdated`, `check`, `lock`, `add`) in a project. Requires a Gemfile and authorization.
201
+ - **`LintTool`** — runs RuboCop or Standard, auto-detected (Standard if `.standard.yml` is present) or forced explicitly. Uses `bundle exec` when a Gemfile exists. Offenses are a normal result, not an error; `autocorrect: true` rewrites files in place. Requires authorization.
202
+ - **`ParseRubyTool`** — outlines a Ruby file's structure (classes, modules, methods, constants with line numbers) via Prism (Ruby 3.3+) or the Ripper stdlib fallback — parses, never executes. Filter with `query`/`kind` to locate a specific definition. Read-only.
203
+
204
+ ```ruby
205
+ gem = SharedTools::Tools::GemTool.new
206
+ gem.execute(name: "ruby_llm", action: "version")
207
+
208
+ bundle = SharedTools::Tools::BundleTool.new(root: "./my-project")
209
+ bundle.execute(action: "add", gem: "rack")
210
+
211
+ lint = SharedTools::Tools::LintTool.new(root: "./my-project")
212
+ lint.execute(path: "lib/foo.rb", autocorrect: true)
213
+
214
+ parse = SharedTools::Tools::ParseRubyTool.new
215
+ parse.execute(path: "./lib/foo.rb")
216
+ parse.execute(path: "./lib/foo.rb", kind: "method", query: "initialize")
217
+ ```
218
+
219
+ ---
220
+
221
+ ### ⚙️ Process Management Tools
222
+
223
+ Start, monitor, and stop long-running background processes (dev servers, watchers, log tails) without blocking the caller.
224
+
225
+ - **`ProcessStartTool`** — starts a command as a background process and returns its id immediately. The executable must be on `allowed_commands` (empty by default — nothing is allowed until configured); argv only, no shell. Runs in its own process group. Requires authorization.
226
+ - **`ProcessListTool`** — lists background processes: id, status, pid, age, command. Read-only.
227
+ - **`ProcessOutputTool`** — reads new stdout/stderr since the last read (poll it to follow output), plus status and exit code. Read-only.
228
+ - **`ProcessKillTool`** — SIGTERM the process group, escalating to SIGKILL if it doesn't exit, then returns final output. Always available with no authorization prompt, so a runaway process can always be stopped.
229
+
230
+ ```ruby
231
+ start = SharedTools::Tools::ProcessStartTool.new(allowed_commands: ["rackup"])
232
+ started = start.execute(command: "rackup", args: ["-p", "9292"], name: "web server")
233
+ id = started[/proc_\d+/]
234
+
235
+ output = SharedTools::Tools::ProcessOutputTool.new
236
+ output.execute(id: id) # poll this in a loop to stream output
237
+
238
+ SharedTools::Tools::ProcessListTool.new.execute
239
+ SharedTools::Tools::ProcessKillTool.new.execute(id: id)
240
+ ```
241
+
242
+ ---
243
+
244
+ ### 🌐 Network Tools
245
+
246
+ Guarded HTTP — every request and redirect hop is checked against SSRF (server-side request forgery): only http/https, no embedded credentials, and the resolved IP is rejected (and the connection pinned to it) if it's private, loopback, link-local, cloud-metadata, or otherwise reserved.
247
+
248
+ - **`WebFetchTool`** — fetches a URL and returns readable text (HTML is stripped to text). Read-only, no authorization needed.
249
+ - **`HttpRequestTool`** — a general HTTP client returning status, key headers, and body. GET/HEAD need no authorization; POST/PUT/PATCH/DELETE require it.
250
+ - **`DownloadFileTool`** — downloads a URL to a file within `root`, size-capped. Requires authorization.
251
+
252
+ ```ruby
253
+ fetch = SharedTools::Tools::WebFetchTool.new
254
+ fetch.execute(url: "https://example.com")
255
+
256
+ request = SharedTools::Tools::HttpRequestTool.new
257
+ request.execute(url: "https://api.example.com/items", method: "POST", body: '{"name":"x"}')
258
+
259
+ download = SharedTools::Tools::DownloadFileTool.new(root: "./downloads")
260
+ download.execute(url: "https://example.com/release.zip", path: "release.zip")
261
+ ```
262
+
263
+ ---
264
+
123
265
  ### 🗄️ Database Tools
124
266
 
125
267
  Execute SQL operations on databases.
@@ -162,6 +304,39 @@ output = eval_tool.execute(language: "shell", code: "ls -la")
162
304
 
163
305
  ---
164
306
 
307
+ ### 🖥️ Bash & Test Runner Tools
308
+
309
+ - **`BashTool`** — runs ONE allowlisted executable with arguments. Deliberately not a shell: no pipes, redirects, globs, or variable expansion, so OS command injection has no surface to exploit. `allowed_commands` is empty by default — nothing runs until configured. Requires authorization.
310
+ - **`RunTestsTool`** — runs a Ruby project's test suite (RSpec or Minitest, auto-detected). A failing suite is a normal result, not a tool error. Requires authorization.
311
+ - **`PythonTestsTool`** — runs a Python project's tests (pytest or unittest). Requires authorization.
312
+
313
+ ```ruby
314
+ bash = SharedTools::Tools::BashTool.new(allowed_commands: ["ls", "cat"])
315
+ bash.execute(command: "ls", args: ["-la"])
316
+
317
+ tests = SharedTools::Tools::RunTestsTool.new(root: "./my-project")
318
+ tests.execute(path: "spec/models/user_spec.rb")
319
+
320
+ py_tests = SharedTools::Tools::PythonTestsTool.new(root: "./my-project")
321
+ py_tests.execute(framework: "unittest")
322
+ ```
323
+
324
+ ---
325
+
326
+ ### ✅ Task Tracking
327
+
328
+ **`TodoWriteTool`** maintains a task list for multi-step work. Each call replaces the whole list and renders it back; state lives on the tool instance for the duration of a chat session. No authorization needed.
329
+
330
+ ```ruby
331
+ todo = SharedTools::Tools::TodoWriteTool.new
332
+ todo.execute(todos: [
333
+ { content: "Write the tool", status: "completed" },
334
+ { content: "Write the tests", status: "in_progress" }
335
+ ])
336
+ ```
337
+
338
+ ---
339
+
165
340
  ### 📄 Doc Tools
166
341
 
167
342
  Read and reason over documents in any format.
@@ -208,6 +383,31 @@ dns.execute(action: "ip_location", host: "8.8.8.8") # geolocate any IP
208
383
 
209
384
  ---
210
385
 
386
+ ### 🧩 Structured Data Tools
387
+
388
+ Query and edit JSON, YAML, TOML, and CSV without writing a parser. Each query tool accepts either a file path or an inline string, and supports a dot/bracket path expression to extract a value (`users[0].name`, `services[].image`, `dependencies.serde.version`) — omit the query to pretty-print the whole document. YAML is loaded with `safe_load` (no arbitrary object deserialization) and TOML is parsed with a small dependency-free parser.
389
+
390
+ ```ruby
391
+ json = SharedTools::Tools::JsonQueryTool.new
392
+ json.execute(path: "./config.json", query: "servers[0].host")
393
+
394
+ yaml = SharedTools::Tools::YamlQueryTool.new
395
+ yaml.execute(path: "./docker-compose.yml", query: "services[].image")
396
+
397
+ toml = SharedTools::Tools::TomlQueryTool.new
398
+ toml.execute(path: "./Cargo.toml", query: "dependencies.serde.version")
399
+
400
+ csv_read = SharedTools::Tools::CsvReadTool.new
401
+ csv_read.execute(path: "./sales.csv", limit: 20)
402
+
403
+ csv_write = SharedTools::Tools::CsvWriteTool.new
404
+ csv_write.execute(path: "./out.csv", headers: ["name", "age"], rows: [["Alice", "30"]])
405
+ ```
406
+
407
+ `CsvWriteTool` is mutating and requires user authorization (see [Authorization System](#authorization-system)); the query tools and `CsvReadTool` are read-only.
408
+
409
+ ---
410
+
211
411
  ### 🌤️ Weather Tool
212
412
 
213
413
  Real-time weather data from OpenWeatherMap API. Combine with DnsTool for automatic local forecasts.
@@ -440,14 +640,16 @@ SharedTools bundles [Model Context Protocol](https://modelcontextprotocol.io) cl
440
640
  | `require 'shared_tools/mcp/playwright_client'` | Browser automation: navigate, click, fill, screenshot, extract |
441
641
 
442
642
  ```ruby
443
- # Load all available clients at once (skips any whose env vars are missing)
444
- require 'shared_tools/mcp'
445
-
446
- # Or load a specific client
643
+ # Each client is a RubyLLM::MCP subclass (ruby_llm >= 2.1). Defining it starts
644
+ # nothing; the server is launched/connected when its tools are first requested.
447
645
  require 'shared_tools/mcp/notion_client'
448
- client = RubyLLM::MCP.clients["notion"]
449
- chat = RubyLLM.chat.with_tools(*client.tools)
646
+ chat = RubyLLM.chat.with_mcp(SharedTools::MCP::Notion)
450
647
  chat.ask("Find my project planning pages and summarise what's in them")
648
+
649
+ # Load every definition; .available skips those whose env vars are missing
650
+ require 'shared_tools/mcp'
651
+ chat = RubyLLM.chat.with_mcp(*SharedTools::MCP.available)
652
+ robot = RobotLab.build(name: "helper", mcp: SharedTools::MCP.available)
451
653
  ```
452
654
 
453
655
  See [MCP Clients README](lib/shared_tools/mcp/README.md) for full configuration details.
@@ -596,6 +798,8 @@ This gem is available as open source under the terms of the [MIT License](LICENS
596
798
 
597
799
  This gem was originally inspired by Kevin Sylvestre's [omniai-tools](https://github.com/ksylvest/omniai-tools) gem. SharedTools has since evolved to focus exclusively on RubyLLM support with enhanced features and comprehensive documentation.
598
800
 
801
+ Also checkout the wonderful ruby gem [ruby_llm-toolbox](https://rubygems.org/gems/ruby_llm-toolbox)
802
+
599
803
  ## Links
600
804
 
601
805
  - **Documentation**: [madbomber.github.io/shared_tools](https://madbomber.github.io/shared_tools)
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/apply_patch_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/apply_patch_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/bash_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/bash_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/bundle_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/bundle_tool'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedTools
4
+ # Validates the executable name for BashTool and ProcessStartTool. The
5
+ # actual execution path uses array-form spawning (no shell), so this guard
6
+ # only has to ensure the program itself is on the allowlist and isn't
7
+ # smuggling a path or shell metacharacters. Arguments are passed verbatim as
8
+ # argv and are therefore inert — there is no shell to interpret them.
9
+ class CommandGuard
10
+ class Blocked < StandardError; end
11
+
12
+ SHELL_META = /[;&|<>`$(){}\[\]*?!#~\n\r]/
13
+ PATH_SEP = %r{[/\\]}
14
+
15
+ def initialize(allowed)
16
+ @allowed = Array(allowed).map(&:to_s)
17
+ end
18
+
19
+ # Returns the validated executable name, or raises Blocked.
20
+ def check!(command)
21
+ cmd = command.to_s
22
+ raise Blocked, "no command given" if cmd.empty?
23
+
24
+ raise Blocked, "executable name may not contain a path: #{cmd.inspect}" if cmd.match?(PATH_SEP)
25
+ raise Blocked, "executable name may not contain shell metacharacters: #{cmd.inspect}" if cmd.match?(SHELL_META)
26
+
27
+ unless @allowed.include?(cmd)
28
+ raise Blocked, "command not allowed: #{cmd.inspect} " \
29
+ "(allowed: #{@allowed.empty? ? '(none)' : @allowed.join(', ')})"
30
+ end
31
+
32
+ cmd
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/csv_read_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/csv_read_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/csv_write_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/csv_write_tool'
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedTools
4
+ # Shared path-navigation for the structured-data query tools (JsonQueryTool,
5
+ # YamlQueryTool, TomlQueryTool). Path syntax: dot-separated keys, [n] for
6
+ # array indices, and [] to map a field across an array. Examples:
7
+ # users[0].name users[].email config.server.port
8
+ module DataPath
9
+ class Error < StandardError; end
10
+
11
+ module_function
12
+
13
+ def query(data, path)
14
+ apply(data, parse(path))
15
+ end
16
+
17
+ def parse(path)
18
+ cleaned = path.to_s.strip.sub(/\A\$?\.?/, "")
19
+ tokens = []
20
+ cleaned.scan(/[^.\[\]]+|\[\d+\]|\[\]/) do |match|
21
+ tokens << case match
22
+ when "[]" then :map
23
+ when /\A\[(\d+)\]\z/ then Regexp.last_match(1).to_i
24
+ else match
25
+ end
26
+ end
27
+ raise Error, "could not parse path: #{path.inspect}" if tokens.empty?
28
+
29
+ tokens
30
+ end
31
+
32
+ def apply(data, tokens)
33
+ return data if tokens.empty? || data.nil?
34
+
35
+ token, *rest = tokens
36
+ case token
37
+ when :map
38
+ raise Error, "[] expects an array, got #{data.class}" unless data.is_a?(Array)
39
+
40
+ data.map { |element| apply(element, rest) }
41
+ when Integer
42
+ raise Error, "index [#{token}] expects an array, got #{data.class}" unless data.is_a?(Array)
43
+
44
+ apply(data[token], rest)
45
+ else
46
+ raise Error, "key '#{token}' expects an object, got #{data.class}" unless data.is_a?(Hash)
47
+
48
+ apply(data[token], rest)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/diff_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/diff_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/download_file_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/download_file_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/gem_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/gem_tool'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/git_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/git' # sub-tools (Git::StatusTool, Git::DiffTool, etc.)
5
+ require 'shared_tools/tools/git_tool' # facade (GitTool)
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/glob_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/glob_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/http_request_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/http_request_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/json_query_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/json_query_tool'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ # Shim: require 'shared_tools/lint_tool'
3
+ require 'shared_tools'
4
+ require 'shared_tools/tools/lint_tool'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # shared_tools/mcp/brave_search_client.rb
2
4
  #
3
5
  # Brave Search MCP Server — npx auto-download (no pre-installation required)
@@ -8,30 +10,30 @@
8
10
  # - Node.js and npx (https://nodejs.org)
9
11
  # - A Brave Search API key (free tier available at https://brave.com/search/api/)
10
12
  # The @modelcontextprotocol/server-brave-search package is downloaded
11
- # automatically on first use via `npx -y`.
13
+ # automatically the first time the server is started, via `npx -y`.
12
14
  #
13
15
  # Configuration:
14
16
  # export BRAVE_API_KEY=your_api_key_here
15
17
  #
16
18
  # Usage:
17
19
  # require 'shared_tools/mcp/brave_search_client'
18
- # client = RubyLLM::MCP.clients["brave-search"]
19
- # chat = RubyLLM.chat.with_tools(*client.tools)
20
+ # chat = RubyLLM.chat.with_mcp(SharedTools::MCP::BraveSearch)
21
+ # robot = RobotLab.build(name: "researcher", mcp: [SharedTools::MCP::BraveSearch])
20
22
  #
21
- # Compatible with ruby_llm-mcp >= 0.7.0
23
+ # Requires ruby_llm >= 2.1 (built-in RubyLLM::MCP). Nothing starts until the
24
+ # server's tools are first requested.
22
25
 
23
- require_relative "../utilities"
26
+ require_relative "client_support"
24
27
 
25
- SharedTools.verify_envars("BRAVE_API_KEY")
28
+ module SharedTools
29
+ module MCP
30
+ class BraveSearch < ::RubyLLM::MCP
31
+ extend ClientSupport
26
32
 
27
- require "ruby_llm/mcp"
33
+ requires_envars "BRAVE_API_KEY"
28
34
 
29
- RubyLLM::MCP.add_client(
30
- name: "brave-search",
31
- transport_type: :stdio,
32
- config: {
33
- command: "npx",
34
- args: ["-y", "@modelcontextprotocol/server-brave-search"],
35
- env: { "BRAVE_API_KEY" => ENV.fetch("BRAVE_API_KEY", "") },
36
- },
37
- )
35
+ command "npx", "-y", "@modelcontextprotocol/server-brave-search"
36
+ env "BRAVE_API_KEY" => env_from("BRAVE_API_KEY")
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # shared_tools/mcp/chart_client.rb
2
4
  #
3
5
  # AntV Chart MCP Server — npx auto-download (no pre-installation required)
@@ -7,26 +9,27 @@
7
9
  #
8
10
  # Prerequisites:
9
11
  # - Node.js and npx (https://nodejs.org)
10
- # The @antv/mcp-server-chart package is downloaded automatically on first use
11
- # via `npx -y`.
12
+ # The @antv/mcp-server-chart package is downloaded automatically the first
13
+ # time the server is started, via `npx -y`.
12
14
  #
13
15
  # No API key required.
14
16
  #
15
17
  # Usage:
16
18
  # require 'shared_tools/mcp/chart_client'
17
- # client = RubyLLM::MCP.clients["chart"]
18
- # chat = RubyLLM.chat.with_tools(*client.tools)
19
+ # chat = RubyLLM.chat.with_mcp(SharedTools::MCP::Chart)
20
+ # robot = RobotLab.build(name: "analyst", mcp: [SharedTools::MCP::Chart])
19
21
  #
20
- # Compatible with ruby_llm-mcp >= 0.7.0
22
+ # Requires ruby_llm >= 2.1 (built-in RubyLLM::MCP). Nothing starts until the
23
+ # server's tools are first requested.
24
+
25
+ require_relative "client_support"
21
26
 
22
- require "ruby_llm/mcp"
27
+ module SharedTools
28
+ module MCP
29
+ class Chart < ::RubyLLM::MCP
30
+ extend ClientSupport
23
31
 
24
- RubyLLM::MCP.add_client(
25
- name: "chart",
26
- transport_type: :stdio,
27
- config: {
28
- command: "npx",
29
- args: ["-y", "@antv/mcp-server-chart"],
30
- env: {},
31
- },
32
- )
32
+ command "npx", "-y", "@antv/mcp-server-chart"
33
+ end
34
+ end
35
+ end