galet-tools 0.1.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.
- galet_tools-0.1.0/LICENSE +21 -0
- galet_tools-0.1.0/PKG-INFO +190 -0
- galet_tools-0.1.0/README.md +166 -0
- galet_tools-0.1.0/pyproject.toml +40 -0
- galet_tools-0.1.0/setup.cfg +4 -0
- galet_tools-0.1.0/src/galet_tools/__init__.py +25 -0
- galet_tools-0.1.0/src/galet_tools/discovery.py +69 -0
- galet_tools-0.1.0/src/galet_tools/framework/__init__.py +12 -0
- galet_tools-0.1.0/src/galet_tools/framework/handler_registry.py +195 -0
- galet_tools-0.1.0/src/galet_tools/framework/handler_v2.py +34 -0
- galet_tools-0.1.0/src/galet_tools/framework/schema_handler_v2.py +178 -0
- galet_tools-0.1.0/src/galet_tools/host/__init__.py +0 -0
- galet_tools-0.1.0/src/galet_tools/host/adapters.py +58 -0
- galet_tools-0.1.0/src/galet_tools/host/config.py +166 -0
- galet_tools-0.1.0/src/galet_tools/host/security.py +103 -0
- galet_tools-0.1.0/src/galet_tools/mcp/__init__.py +0 -0
- galet_tools-0.1.0/src/galet_tools/mcp/server.py +227 -0
- galet_tools-0.1.0/src/galet_tools/mcp/smoke.py +144 -0
- galet_tools-0.1.0/src/galet_tools/mcp/tool_adapter.py +143 -0
- galet_tools-0.1.0/src/galet_tools/ports/__init__.py +0 -0
- galet_tools-0.1.0/src/galet_tools/ports/config_provider.py +20 -0
- galet_tools-0.1.0/src/galet_tools/ports/sandbox_root_resolver.py +15 -0
- galet_tools-0.1.0/src/galet_tools/ports/security_policy.py +19 -0
- galet_tools-0.1.0/src/galet_tools/ports/storage_location_resolver.py +26 -0
- galet_tools-0.1.0/src/galet_tools/register_generic_defaults.py +23 -0
- galet_tools-0.1.0/src/galet_tools/tools/__init__.py +0 -0
- galet_tools-0.1.0/src/galet_tools/tools/command_execution_handler2.py +488 -0
- galet_tools-0.1.0/src/galet_tools/tools/file_load_handler2.py +390 -0
- galet_tools-0.1.0/src/galet_tools/tools/file_save_handler.py +279 -0
- galet_tools-0.1.0/src/galet_tools/tools/generate_image_handler.py +188 -0
- galet_tools-0.1.0/src/galet_tools/tools/generate_svg_handler.py +352 -0
- galet_tools-0.1.0/src/galet_tools/tools/patch_apply_handler.py +478 -0
- galet_tools-0.1.0/src/galet_tools/tools/repo_index_handler.py +78 -0
- galet_tools-0.1.0/src/galet_tools/tools/repo_search_handler.py +69 -0
- galet_tools-0.1.0/src/galet_tools/tools/repository_handler_support.py +43 -0
- galet_tools-0.1.0/src/galet_tools/tools/repository_index.py +226 -0
- galet_tools-0.1.0/src/galet_tools.egg-info/PKG-INFO +190 -0
- galet_tools-0.1.0/src/galet_tools.egg-info/SOURCES.txt +57 -0
- galet_tools-0.1.0/src/galet_tools.egg-info/dependency_links.txt +1 -0
- galet_tools-0.1.0/src/galet_tools.egg-info/entry_points.txt +3 -0
- galet_tools-0.1.0/src/galet_tools.egg-info/requires.txt +5 -0
- galet_tools-0.1.0/src/galet_tools.egg-info/top_level.txt +1 -0
- galet_tools-0.1.0/tests/test_command_execution_handler2.py +144 -0
- galet_tools-0.1.0/tests/test_discovery.py +128 -0
- galet_tools-0.1.0/tests/test_execute_command_fail_fast.py +84 -0
- galet_tools-0.1.0/tests/test_file_load_handler2.py +272 -0
- galet_tools-0.1.0/tests/test_file_save_handler.py +143 -0
- galet_tools-0.1.0/tests/test_generate_image_handler.py +111 -0
- galet_tools-0.1.0/tests/test_generate_svg_handler.py +195 -0
- galet_tools-0.1.0/tests/test_handler_registry_factory.py +69 -0
- galet_tools-0.1.0/tests/test_host_config.py +159 -0
- galet_tools-0.1.0/tests/test_mcp_server_dispatch.py +139 -0
- galet_tools-0.1.0/tests/test_mcp_server_dispatch_errors.py +78 -0
- galet_tools-0.1.0/tests/test_mcp_server_scope.py +116 -0
- galet_tools-0.1.0/tests/test_patch_apply_handler.py +221 -0
- galet_tools-0.1.0/tests/test_permission_filtering.py +103 -0
- galet_tools-0.1.0/tests/test_register_generic_defaults.py +91 -0
- galet_tools-0.1.0/tests/test_repository_navigation_handlers.py +78 -0
- galet_tools-0.1.0/tests/test_tool_adapter.py +248 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 junwin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: galet-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Self-contained generic tool handlers and handler framework extracted from the Lucy monorepo
|
|
5
|
+
Author-email: junwin <jdunwin@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/junwin/galet-tools
|
|
8
|
+
Project-URL: Repository, https://github.com/junwin/galet-tools
|
|
9
|
+
Keywords: tools,handlers,files,svg,image-generation
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: Pillow
|
|
20
|
+
Requires-Dist: pydantic>=2
|
|
21
|
+
Provides-Extra: mcp
|
|
22
|
+
Requires-Dist: mcp==2.1.1; extra == "mcp"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# galet-tools
|
|
26
|
+
|
|
27
|
+
Self-contained generic tool handlers and the handler framework they run on.
|
|
28
|
+
|
|
29
|
+
## Lineage
|
|
30
|
+
|
|
31
|
+
This package is extracted from the Lucy monorepo (`src/handlers`) as described
|
|
32
|
+
in the Lucy design doc `software/ai/lucy/design/handlers-extraction.md` and
|
|
33
|
+
the plan `software/ai/lucy/plan/handlers-extraction.md`.
|
|
34
|
+
|
|
35
|
+
It is a fresh repository with no shared git history. Handler logic moves
|
|
36
|
+
verbatim; the only intentional changes are the import namespace
|
|
37
|
+
(`src.handlers.*` -> `galet_tools.*`) and the configuration boundary: tools
|
|
38
|
+
depend on narrow port interfaces owned by this package (`ConfigProvider`,
|
|
39
|
+
`StorageLocationResolver`, `SandboxRootResolver`, `SecurityPolicy`, ...),
|
|
40
|
+
never on Lucy's `ConfigManager`. Lucy implements those ports at its
|
|
41
|
+
composition root.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- **Handler framework** — `HandlerV2` ABC, `HandlerRegistry`, schema
|
|
46
|
+
handling, and permission filtering.
|
|
47
|
+
- **Generic tools** — `file_load`, `file_save`, `patch_apply`,
|
|
48
|
+
`execute_command`, `generate_svg`, and `generate_image`, registered
|
|
49
|
+
under their existing names. `file_load` supports bounded line-range reads.
|
|
50
|
+
- **Ports** — sibling-owned interfaces the host implements, keeping the tools
|
|
51
|
+
free of any host dependency.
|
|
52
|
+
- **Handler discovery** — installed packages can contribute handlers through
|
|
53
|
+
Python package entry points without changing Lucy or galet-tools.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python -m venv .venv
|
|
59
|
+
.venv/bin/pip install -e .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run the suite:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python -m pytest -q
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Using the registry in a host
|
|
69
|
+
|
|
70
|
+
Galet owns the handler contract and base registry. A host such as Lucy composes
|
|
71
|
+
the generic handlers, its own host-specific handlers, and installed extension
|
|
72
|
+
packages:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from galet_tools import HandlerRegistry, register_installed_handlers
|
|
76
|
+
from galet_tools.register_generic_defaults import register_generic_defaults
|
|
77
|
+
|
|
78
|
+
registry = HandlerRegistry()
|
|
79
|
+
register_generic_defaults(registry)
|
|
80
|
+
register_lucy_handlers(registry)
|
|
81
|
+
register_installed_handlers(registry)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Discovery adds installed capabilities to the registry; it does not grant them
|
|
85
|
+
to an agent. The host should continue to apply its agent and context allowlists
|
|
86
|
+
when selecting tool definitions.
|
|
87
|
+
|
|
88
|
+
### Creating a handler extension
|
|
89
|
+
|
|
90
|
+
An extension package declares a registrar in its `pyproject.toml`:
|
|
91
|
+
|
|
92
|
+
```toml
|
|
93
|
+
[project.entry-points."galet_tools.handlers"]
|
|
94
|
+
web = "galet_web_tools:register"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The target is a callable that receives the shared registry:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
def register(registry):
|
|
101
|
+
registry.register(SearchHandler)
|
|
102
|
+
registry.register(FetchHandler)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Entry points are loaded in deterministic name order. A plugin that cannot be
|
|
106
|
+
loaded, does not resolve to a callable, or fails during registration raises
|
|
107
|
+
`HandlerPluginError` with the responsible entry-point name. This is
|
|
108
|
+
intentionally fail-fast so a host cannot start with an unexpectedly incomplete
|
|
109
|
+
tool set.
|
|
110
|
+
|
|
111
|
+
## MCP server
|
|
112
|
+
|
|
113
|
+
An optional MCP (Model Context Protocol) server exposes the six generic
|
|
114
|
+
tools over streamable HTTP. Install the extra:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
.venv/bin/pip install -e '.[mcp]'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Copy the example config and start the server:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cp galet-mcp.example.json galet-mcp.json
|
|
124
|
+
galet-tools-mcp
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`galet-tools-mcp` reads `galet-mcp.json` from the current directory by
|
|
128
|
+
default; point it elsewhere with `--config <path>`.
|
|
129
|
+
|
|
130
|
+
### Configuration
|
|
131
|
+
|
|
132
|
+
`galet-mcp.json` mirrors `galet-mcp.example.json`. Required keys are
|
|
133
|
+
`account`, `storage_root`, `storage_namespace`, and `sandbox_root`; a
|
|
134
|
+
null, blank, or (for the two path keys) relative value fails validation.
|
|
135
|
+
Optional keys left out or set to null fall back to their defaults:
|
|
136
|
+
|
|
137
|
+
- `allowlist` — tools to expose; default is all six (`file_load`,
|
|
138
|
+
`file_save`, `patch_apply`, `execute_command`, `generate_svg`,
|
|
139
|
+
`generate_image`).
|
|
140
|
+
- `external_roots` — named external roots the tools may access; default
|
|
141
|
+
none.
|
|
142
|
+
- `host` — default `127.0.0.1`; loopback only.
|
|
143
|
+
- `port` — default `8765`.
|
|
144
|
+
- `fonts` — font paths for the generators; default none.
|
|
145
|
+
|
|
146
|
+
### What it exposes
|
|
147
|
+
|
|
148
|
+
`tools/list` and `tools/call` over streamable HTTP at
|
|
149
|
+
`http://127.0.0.1:8765/mcp`.
|
|
150
|
+
|
|
151
|
+
### Smoke-test from the CLI
|
|
152
|
+
|
|
153
|
+
`galet-tools-smoke` is the end-to-end CLI client: it connects over HTTP
|
|
154
|
+
(streamable HTTP) to a running galet MCP server using the official MCP SDK
|
|
155
|
+
and performs real `tools/list` and `tools/call` round trips.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
galet-tools-smoke list
|
|
159
|
+
galet-tools-smoke list --json
|
|
160
|
+
galet-tools-smoke call <tool> '<json args>'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The default URL is `http://127.0.0.1:8765/mcp`; override it with `--url`.
|
|
164
|
+
The default read timeout is 30s; override it with `--timeout`.
|
|
165
|
+
|
|
166
|
+
Example round trip (storage root from `galet-mcp.json`):
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
galet-tools-smoke call file_save '{"path":"smoke/hello.txt","file_content":"hello galet smoke"}'
|
|
170
|
+
galet-tools-smoke call file_load '{"path":"smoke/hello.txt"}'
|
|
171
|
+
galet-tools-smoke call execute_command '{"command":"pwd","working_directory":"."}'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Exit codes: `0` ok; `1` tool reported an error (`isError`); `2` usage or
|
|
175
|
+
bad JSON; `3` could not reach the server.
|
|
176
|
+
|
|
177
|
+
### Fail-closed behaviour
|
|
178
|
+
|
|
179
|
+
The server refuses to start when the allowlist is empty or has no overlap
|
|
180
|
+
with the registered tools, and only the advertised tools are callable.
|
|
181
|
+
Unknown tool names in the allowlist fail validation.
|
|
182
|
+
|
|
183
|
+
### Security
|
|
184
|
+
|
|
185
|
+
Binding is loopback-only by design: `host` must resolve to a loopback
|
|
186
|
+
address. Remote access needs a separate reverse-proxy design.
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# galet-tools
|
|
2
|
+
|
|
3
|
+
Self-contained generic tool handlers and the handler framework they run on.
|
|
4
|
+
|
|
5
|
+
## Lineage
|
|
6
|
+
|
|
7
|
+
This package is extracted from the Lucy monorepo (`src/handlers`) as described
|
|
8
|
+
in the Lucy design doc `software/ai/lucy/design/handlers-extraction.md` and
|
|
9
|
+
the plan `software/ai/lucy/plan/handlers-extraction.md`.
|
|
10
|
+
|
|
11
|
+
It is a fresh repository with no shared git history. Handler logic moves
|
|
12
|
+
verbatim; the only intentional changes are the import namespace
|
|
13
|
+
(`src.handlers.*` -> `galet_tools.*`) and the configuration boundary: tools
|
|
14
|
+
depend on narrow port interfaces owned by this package (`ConfigProvider`,
|
|
15
|
+
`StorageLocationResolver`, `SandboxRootResolver`, `SecurityPolicy`, ...),
|
|
16
|
+
never on Lucy's `ConfigManager`. Lucy implements those ports at its
|
|
17
|
+
composition root.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Handler framework** — `HandlerV2` ABC, `HandlerRegistry`, schema
|
|
22
|
+
handling, and permission filtering.
|
|
23
|
+
- **Generic tools** — `file_load`, `file_save`, `patch_apply`,
|
|
24
|
+
`execute_command`, `generate_svg`, and `generate_image`, registered
|
|
25
|
+
under their existing names. `file_load` supports bounded line-range reads.
|
|
26
|
+
- **Ports** — sibling-owned interfaces the host implements, keeping the tools
|
|
27
|
+
free of any host dependency.
|
|
28
|
+
- **Handler discovery** — installed packages can contribute handlers through
|
|
29
|
+
Python package entry points without changing Lucy or galet-tools.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python -m venv .venv
|
|
35
|
+
.venv/bin/pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run the suite:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pytest -q
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Using the registry in a host
|
|
45
|
+
|
|
46
|
+
Galet owns the handler contract and base registry. A host such as Lucy composes
|
|
47
|
+
the generic handlers, its own host-specific handlers, and installed extension
|
|
48
|
+
packages:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from galet_tools import HandlerRegistry, register_installed_handlers
|
|
52
|
+
from galet_tools.register_generic_defaults import register_generic_defaults
|
|
53
|
+
|
|
54
|
+
registry = HandlerRegistry()
|
|
55
|
+
register_generic_defaults(registry)
|
|
56
|
+
register_lucy_handlers(registry)
|
|
57
|
+
register_installed_handlers(registry)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Discovery adds installed capabilities to the registry; it does not grant them
|
|
61
|
+
to an agent. The host should continue to apply its agent and context allowlists
|
|
62
|
+
when selecting tool definitions.
|
|
63
|
+
|
|
64
|
+
### Creating a handler extension
|
|
65
|
+
|
|
66
|
+
An extension package declares a registrar in its `pyproject.toml`:
|
|
67
|
+
|
|
68
|
+
```toml
|
|
69
|
+
[project.entry-points."galet_tools.handlers"]
|
|
70
|
+
web = "galet_web_tools:register"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The target is a callable that receives the shared registry:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
def register(registry):
|
|
77
|
+
registry.register(SearchHandler)
|
|
78
|
+
registry.register(FetchHandler)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Entry points are loaded in deterministic name order. A plugin that cannot be
|
|
82
|
+
loaded, does not resolve to a callable, or fails during registration raises
|
|
83
|
+
`HandlerPluginError` with the responsible entry-point name. This is
|
|
84
|
+
intentionally fail-fast so a host cannot start with an unexpectedly incomplete
|
|
85
|
+
tool set.
|
|
86
|
+
|
|
87
|
+
## MCP server
|
|
88
|
+
|
|
89
|
+
An optional MCP (Model Context Protocol) server exposes the six generic
|
|
90
|
+
tools over streamable HTTP. Install the extra:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
.venv/bin/pip install -e '.[mcp]'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Copy the example config and start the server:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cp galet-mcp.example.json galet-mcp.json
|
|
100
|
+
galet-tools-mcp
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`galet-tools-mcp` reads `galet-mcp.json` from the current directory by
|
|
104
|
+
default; point it elsewhere with `--config <path>`.
|
|
105
|
+
|
|
106
|
+
### Configuration
|
|
107
|
+
|
|
108
|
+
`galet-mcp.json` mirrors `galet-mcp.example.json`. Required keys are
|
|
109
|
+
`account`, `storage_root`, `storage_namespace`, and `sandbox_root`; a
|
|
110
|
+
null, blank, or (for the two path keys) relative value fails validation.
|
|
111
|
+
Optional keys left out or set to null fall back to their defaults:
|
|
112
|
+
|
|
113
|
+
- `allowlist` — tools to expose; default is all six (`file_load`,
|
|
114
|
+
`file_save`, `patch_apply`, `execute_command`, `generate_svg`,
|
|
115
|
+
`generate_image`).
|
|
116
|
+
- `external_roots` — named external roots the tools may access; default
|
|
117
|
+
none.
|
|
118
|
+
- `host` — default `127.0.0.1`; loopback only.
|
|
119
|
+
- `port` — default `8765`.
|
|
120
|
+
- `fonts` — font paths for the generators; default none.
|
|
121
|
+
|
|
122
|
+
### What it exposes
|
|
123
|
+
|
|
124
|
+
`tools/list` and `tools/call` over streamable HTTP at
|
|
125
|
+
`http://127.0.0.1:8765/mcp`.
|
|
126
|
+
|
|
127
|
+
### Smoke-test from the CLI
|
|
128
|
+
|
|
129
|
+
`galet-tools-smoke` is the end-to-end CLI client: it connects over HTTP
|
|
130
|
+
(streamable HTTP) to a running galet MCP server using the official MCP SDK
|
|
131
|
+
and performs real `tools/list` and `tools/call` round trips.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
galet-tools-smoke list
|
|
135
|
+
galet-tools-smoke list --json
|
|
136
|
+
galet-tools-smoke call <tool> '<json args>'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The default URL is `http://127.0.0.1:8765/mcp`; override it with `--url`.
|
|
140
|
+
The default read timeout is 30s; override it with `--timeout`.
|
|
141
|
+
|
|
142
|
+
Example round trip (storage root from `galet-mcp.json`):
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
galet-tools-smoke call file_save '{"path":"smoke/hello.txt","file_content":"hello galet smoke"}'
|
|
146
|
+
galet-tools-smoke call file_load '{"path":"smoke/hello.txt"}'
|
|
147
|
+
galet-tools-smoke call execute_command '{"command":"pwd","working_directory":"."}'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Exit codes: `0` ok; `1` tool reported an error (`isError`); `2` usage or
|
|
151
|
+
bad JSON; `3` could not reach the server.
|
|
152
|
+
|
|
153
|
+
### Fail-closed behaviour
|
|
154
|
+
|
|
155
|
+
The server refuses to start when the allowlist is empty or has no overlap
|
|
156
|
+
with the registered tools, and only the advertised tools are callable.
|
|
157
|
+
Unknown tool names in the allowlist fail validation.
|
|
158
|
+
|
|
159
|
+
### Security
|
|
160
|
+
|
|
161
|
+
Binding is loopback-only by design: `host` must resolve to a loopback
|
|
162
|
+
address. Remote access needs a separate reverse-proxy design.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "galet-tools"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Self-contained generic tool handlers and handler framework extracted from the Lucy monorepo"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "junwin", email = "jdunwin@gmail.com" }]
|
|
13
|
+
keywords = ["tools", "handlers", "files", "svg", "image-generation"]
|
|
14
|
+
dependencies = ["Pillow", "pydantic>=2"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
mcp = ["mcp==2.1.1"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
galet-tools-mcp = "galet_tools.mcp.server:main"
|
|
29
|
+
galet-tools-smoke = "galet_tools.mcp.smoke:main"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/junwin/galet-tools"
|
|
33
|
+
Repository = "https://github.com/junwin/galet-tools"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .discovery import (
|
|
2
|
+
ENTRY_POINT_GROUP,
|
|
3
|
+
HandlerPluginError,
|
|
4
|
+
register_installed_handlers,
|
|
5
|
+
)
|
|
6
|
+
from .framework import (
|
|
7
|
+
ErrorCode,
|
|
8
|
+
HandlerRegistry,
|
|
9
|
+
HandlerV2,
|
|
10
|
+
ResultEnvelope,
|
|
11
|
+
SchemaHandlerV2,
|
|
12
|
+
filter_eligible_tool_defs,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ENTRY_POINT_GROUP",
|
|
17
|
+
"ErrorCode",
|
|
18
|
+
"HandlerPluginError",
|
|
19
|
+
"HandlerRegistry",
|
|
20
|
+
"HandlerV2",
|
|
21
|
+
"ResultEnvelope",
|
|
22
|
+
"SchemaHandlerV2",
|
|
23
|
+
"filter_eligible_tool_defs",
|
|
24
|
+
"register_installed_handlers",
|
|
25
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import entry_points
|
|
4
|
+
from typing import Iterable
|
|
5
|
+
|
|
6
|
+
from galet_tools.framework.handler_registry import HandlerRegistry
|
|
7
|
+
|
|
8
|
+
ENTRY_POINT_GROUP = "galet_tools.handlers"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HandlerPluginError(RuntimeError):
|
|
12
|
+
"""Raised when an installed handler plugin cannot be registered."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _handler_entry_points(group: str) -> Iterable[object]:
|
|
16
|
+
"""Return handler entry points in deterministic order.
|
|
17
|
+
|
|
18
|
+
The fallback supports the mapping-style object returned by older
|
|
19
|
+
importlib-metadata releases, even though galet-tools itself requires
|
|
20
|
+
Python 3.11 or newer.
|
|
21
|
+
"""
|
|
22
|
+
discovered = entry_points()
|
|
23
|
+
if hasattr(discovered, "select"):
|
|
24
|
+
selected = discovered.select(group=group)
|
|
25
|
+
else: # pragma: no cover - compatibility with old importlib-metadata
|
|
26
|
+
selected = discovered.get(group, ())
|
|
27
|
+
return sorted(selected, key=lambda ep: (ep.name, ep.value))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def register_installed_handlers(
|
|
31
|
+
registry: HandlerRegistry,
|
|
32
|
+
*,
|
|
33
|
+
group: str = ENTRY_POINT_GROUP,
|
|
34
|
+
) -> list[str]:
|
|
35
|
+
"""Load installed handler registrars and apply them to ``registry``.
|
|
36
|
+
|
|
37
|
+
Packages contribute a callable through the ``galet_tools.handlers``
|
|
38
|
+
entry-point group. The callable receives the registry and registers one
|
|
39
|
+
or more HandlerV2 classes. Broken plugins fail startup with the entry-point
|
|
40
|
+
name included, rather than silently removing tools from the host.
|
|
41
|
+
|
|
42
|
+
Returns the entry-point names in the order they were invoked.
|
|
43
|
+
"""
|
|
44
|
+
loaded: list[str] = []
|
|
45
|
+
for entry_point in _handler_entry_points(group):
|
|
46
|
+
try:
|
|
47
|
+
registrar = entry_point.load()
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
raise HandlerPluginError(
|
|
50
|
+
f"Could not load handler plugin {entry_point.name!r} "
|
|
51
|
+
f"({entry_point.value})"
|
|
52
|
+
) from exc
|
|
53
|
+
|
|
54
|
+
if not callable(registrar):
|
|
55
|
+
raise HandlerPluginError(
|
|
56
|
+
f"Handler plugin {entry_point.name!r} ({entry_point.value}) "
|
|
57
|
+
"must resolve to a callable registrar"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
registrar(registry)
|
|
62
|
+
except Exception as exc:
|
|
63
|
+
raise HandlerPluginError(
|
|
64
|
+
f"Handler plugin {entry_point.name!r} ({entry_point.value}) "
|
|
65
|
+
"failed during registration"
|
|
66
|
+
) from exc
|
|
67
|
+
loaded.append(entry_point.name)
|
|
68
|
+
|
|
69
|
+
return loaded
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .handler_registry import HandlerRegistry, filter_eligible_tool_defs
|
|
2
|
+
from .handler_v2 import HandlerV2
|
|
3
|
+
from .schema_handler_v2 import ErrorCode, ResultEnvelope, SchemaHandlerV2
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ErrorCode",
|
|
7
|
+
"HandlerRegistry",
|
|
8
|
+
"HandlerV2",
|
|
9
|
+
"ResultEnvelope",
|
|
10
|
+
"SchemaHandlerV2",
|
|
11
|
+
"filter_eligible_tool_defs",
|
|
12
|
+
]
|