toolfuncs 1.0.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/PKG-INFO +52 -9
  2. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/README.md +51 -8
  3. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/docs/contract.md +8 -2
  4. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/pyproject.toml +1 -1
  5. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/__main__.py +37 -5
  6. toolfuncs-1.2.0/src/toolfuncs/_catalog.py +447 -0
  7. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_contract.py +8 -3
  8. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_mcp.py +37 -3
  9. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_packages.py +11 -2
  10. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_tools.py +1 -1
  11. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/agent_hooks.py +36 -5
  12. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/sdk.py +12 -0
  13. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_agent_hooks.py +21 -0
  14. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_packages.py +71 -0
  15. toolfuncs-1.2.0/tests/test_path_mcp.py +306 -0
  16. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/uv.lock +1 -1
  17. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/.github/workflows/release.yml +0 -0
  18. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/.github/workflows/test.yml +0 -0
  19. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/.gitignore +0 -0
  20. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/.python-version +0 -0
  21. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/docs/migrating-to-1.0.md +0 -0
  22. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/__init__.py +0 -0
  23. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_descriptions.py +0 -0
  24. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_doctor.py +0 -0
  25. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_errors.py +0 -0
  26. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_import_path.py +0 -0
  27. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_runtime.py +0 -0
  28. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/_setup.py +0 -0
  29. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/src/toolfuncs/py.typed +0 -0
  30. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/__init__.py +0 -0
  31. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/conftest.py +0 -0
  32. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_contract.py +0 -0
  33. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_doctor.py +0 -0
  34. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_import_path.py +0 -0
  35. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_managed_runtime.py +0 -0
  36. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_mcp.py +0 -0
  37. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_sdk_cli.py +0 -0
  38. {toolfuncs-1.0.0 → toolfuncs-1.2.0}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: toolfuncs
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Typed Python functions with native Python, CLI, and MCP interfaces
5
5
  Project-URL: Repository, https://github.com/nimashoghi/toolfuncs
6
6
  Project-URL: Issues, https://github.com/nimashoghi/toolfuncs/issues
@@ -185,24 +185,55 @@ Function commands inherit help and error presentation from their parent app. All
185
185
 
186
186
  ## Use MCP
187
187
 
188
- Register each script or opted-in package executable explicitly in your MCP client. For example, a client using an `mcpServers` configuration can launch:
188
+ Register one server to expose the effective PATH catalog. Scripts and opted-in package executables remain separate tool families:
189
189
 
190
190
  ```json
191
191
  {
192
192
  "mcpServers": {
193
- "weather": {
193
+ "toolfuncs": {
194
194
  "command": "/absolute/path/to/toolfuncs",
195
- "args": ["mcp", "/absolute/path/to/weather-report"]
195
+ "args": ["mcp"]
196
196
  }
197
197
  }
198
198
  }
199
199
  ```
200
200
 
201
- `toolfuncs mcp SOURCE` prepares the same isolated dependency environment as the CLI and starts one stdio server. Use `--prepared` only when the selected interpreter already contains the source's dependencies. Toolfuncs does not edit client configuration. Each server exposes all registered operations; restart it after source or dependency changes.
201
+ `toolfuncs mcp` captures PATH and starts one persistent child per family, using each source's isolated dependency environment. Children inherit the launch directory and environment. Startup is bounded to 120 seconds, including dependency preparation, with at most eight families starting concurrently. Configure the client's startup timeout to at least 180 seconds for cold installations. Failed families appear in `catalog` with an error; healthy families remain callable. `toolfuncs doctor` loads all families and exits nonzero if any are unavailable. It does not invoke their operations. Use `toolfuncs doctor SOURCE` for the full source documentation and help checks.
202
+
203
+ The server's operation catalog remains fixed until restart. A crashed child becomes unavailable and is not restarted automatically. Interrupted operations are never replayed, since their side effects may already have occurred. Ordinary function exceptions remain tool errors and leave the family available. Shutdown closes the children. Installing tools, changing PATH, or updating source/dependencies requires restarting the server.
204
+
205
+ `toolfuncs mcp SOURCE` remains available to serve one source or opted-in package command. Use `--prepared` only when the selected interpreter already contains every selected source's dependencies. Toolfuncs does not edit client configuration.
206
+
207
+ ### Progressive discovery
208
+
209
+ The hook advertises families. Inspect one group at a time, then one selected operation:
210
+
211
+ ```console
212
+ toolfuncs catalog
213
+ toolfuncs commands weather-report
214
+ toolfuncs commands weather-report --group '["records"]'
215
+ toolfuncs inspect-operation weather-report '["records", "show"]'
216
+ ```
217
+
218
+ The generic MCP server exposes these same three discovery functions as `catalog`, `commands`, and `inspect_operation`. Group views contain only immediate child names and short descriptions. Selected operation views contain the original input/output schemas, documentation, canonical CLI argv, MCP name, and a Python attribute when it is verifiably exported by the source module. Do not reconstruct Python access from an MCP name: registered aliases and nested CLI groups need not match Python attributes.
219
+
220
+ Python uses the same bound functions:
221
+
222
+ ```python
223
+ from toolfuncs.sdk import ToolCatalog
224
+
225
+ async with ToolCatalog() as catalog:
226
+ families = catalog.catalog() # static discovery, no child startup
227
+ await catalog.start(["weather-report"])
228
+ children = catalog.commands("weather-report")
229
+ detail = catalog.inspect_operation("weather-report", ["records", "show"])
230
+ ```
231
+
232
+ In Codex Code Mode, use toolfuncs discovery to select an operation, resolve its runtime name from `ALL_TOOLS`, and invoke `tools[name](arguments)`. Retain deferred MCP exposure; native tool search is unnecessary. In Claude Code, keep the server's tools deferred (`"alwaysLoad": false` on supported versions), load the selected tool with `ToolSearch` query `select:mcp__toolfuncs__NAME`, then call it normally. Claude still exposes operation names initially. These context policies belong to the client; generic MCP clients may expose all schemas. For the agent hook's client-specific guidance, set `TOOLFUNCS_MCP_SERVER=toolfuncs` in the host environment when that server is registered.
202
233
 
203
234
  For programmatic use, `server = app.to_mcp()` returns a FastMCP server. Sync functions use FastMCP's thread dispatch and async functions are awaited. FastMCP owns request handling and cancellation. MCP calls do not share live state with an IPython kernel. The function implementation is shared.
204
235
 
205
- A nested CLI command such as `records show` becomes `records__show`; hyphens become underscores. Aliases remain CLI aliases. Ambiguous or invalid MCP names fail at startup. Every successful MCP result has structured content `{"result": value}`, including scalar and null results, plus JSON text content. Exceptions become MCP tool errors.
236
+ A nested CLI command such as `records show` becomes `records__show`; the PATH server prefixes the family, for example `weather_report__records__show`. Hyphens become underscores. Aliases remain CLI aliases. Collisions and names exceeding 64 characters fail explicitly. Every successful MCP result has structured content `{"result": value}`, including scalar and null results, plus JSON text content. Exceptions become MCP tool errors.
206
237
 
207
238
  Use stderr for logs and diagnostics. Stdout belongs to the CLI result or MCP protocol; an operation that prints to stdout can corrupt that stream. Durable background tasks, push notifications, remote hosting, resources and prompts are outside the 1.0 interface.
208
239
 
@@ -234,7 +265,7 @@ Declare `toolfuncs>=1,<2` in PEP 723 dependencies to make the required major ver
234
265
 
235
266
  The description, either `[tool.toolfuncs].description` or a package module's first docstring line, is the line agents read in `toolfuncs list` and session-start catalogs when they choose a tool. Write one sentence that says what the tool does for its caller. Keep implementation details, compatibility notes and contract caveats out of it; put them in later docstring text or in command docstrings.
236
267
 
237
- Descriptions are static by default. A tool whose available domain surface changes at runtime may opt into a last-known catalog description with `dynamic = true`:
268
+ Descriptions are static by default. A script whose available domain surface changes at runtime may opt into a last-known catalog description with `dynamic = true`:
238
269
 
239
270
  ```toml
240
271
  [tool.toolfuncs]
@@ -262,6 +293,15 @@ Only functions registered on `app` become CLI commands. Imported functions and `
262
293
 
263
294
  No `if __name__ == "__main__":` block is required. The operating system passes the source path to the shebang interpreter, and toolfuncs imports the source under its declared Python name before invoking its module-level app. Consequently, `weather-report ...` is the supported CLI while `python weather-report ...` merely defines the module and exits, or fails if its dependencies are not already installed.
264
295
 
296
+ Packaged tools opt in with an additional matching entry point:
297
+
298
+ ```toml
299
+ [project.entry-points."toolfuncs.dynamic_description"]
300
+ my-tool = "my_package.tools:app"
301
+ ```
302
+
303
+ Keep the matching `project.scripts` and `project.entry-points.toolfuncs` declarations. Publish descriptions through the same `toolsdk.publish_description` API. Discovery reads the current user's published line on each call and falls back to the module docstring when no valid line exists; it does not import the tool or access its service.
304
+
265
305
  ## Package-backed implementations
266
306
 
267
307
  A toolfunc remains one script even when most of its implementation lives in a package:
@@ -332,9 +372,12 @@ Setup does not discover, copy, register, link, synchronize, or remove tools. The
332
372
 
333
373
  ```console
334
374
  toolfuncs SOURCE [ARGS...]
335
- toolfuncs doctor SOURCE
336
- toolfuncs mcp [--prepared] SOURCE
375
+ toolfuncs doctor [--prepared] [SOURCE]
376
+ toolfuncs mcp [--prepared] [SOURCE]
337
377
  toolfuncs list
378
+ toolfuncs catalog
379
+ toolfuncs commands FAMILY [--group JSON_PATH]
380
+ toolfuncs inspect-operation FAMILY JSON_PATH
338
381
  toolfuncs setup [--bin-dir DIRECTORY]
339
382
  ```
340
383
 
@@ -167,24 +167,55 @@ Function commands inherit help and error presentation from their parent app. All
167
167
 
168
168
  ## Use MCP
169
169
 
170
- Register each script or opted-in package executable explicitly in your MCP client. For example, a client using an `mcpServers` configuration can launch:
170
+ Register one server to expose the effective PATH catalog. Scripts and opted-in package executables remain separate tool families:
171
171
 
172
172
  ```json
173
173
  {
174
174
  "mcpServers": {
175
- "weather": {
175
+ "toolfuncs": {
176
176
  "command": "/absolute/path/to/toolfuncs",
177
- "args": ["mcp", "/absolute/path/to/weather-report"]
177
+ "args": ["mcp"]
178
178
  }
179
179
  }
180
180
  }
181
181
  ```
182
182
 
183
- `toolfuncs mcp SOURCE` prepares the same isolated dependency environment as the CLI and starts one stdio server. Use `--prepared` only when the selected interpreter already contains the source's dependencies. Toolfuncs does not edit client configuration. Each server exposes all registered operations; restart it after source or dependency changes.
183
+ `toolfuncs mcp` captures PATH and starts one persistent child per family, using each source's isolated dependency environment. Children inherit the launch directory and environment. Startup is bounded to 120 seconds, including dependency preparation, with at most eight families starting concurrently. Configure the client's startup timeout to at least 180 seconds for cold installations. Failed families appear in `catalog` with an error; healthy families remain callable. `toolfuncs doctor` loads all families and exits nonzero if any are unavailable. It does not invoke their operations. Use `toolfuncs doctor SOURCE` for the full source documentation and help checks.
184
+
185
+ The server's operation catalog remains fixed until restart. A crashed child becomes unavailable and is not restarted automatically. Interrupted operations are never replayed, since their side effects may already have occurred. Ordinary function exceptions remain tool errors and leave the family available. Shutdown closes the children. Installing tools, changing PATH, or updating source/dependencies requires restarting the server.
186
+
187
+ `toolfuncs mcp SOURCE` remains available to serve one source or opted-in package command. Use `--prepared` only when the selected interpreter already contains every selected source's dependencies. Toolfuncs does not edit client configuration.
188
+
189
+ ### Progressive discovery
190
+
191
+ The hook advertises families. Inspect one group at a time, then one selected operation:
192
+
193
+ ```console
194
+ toolfuncs catalog
195
+ toolfuncs commands weather-report
196
+ toolfuncs commands weather-report --group '["records"]'
197
+ toolfuncs inspect-operation weather-report '["records", "show"]'
198
+ ```
199
+
200
+ The generic MCP server exposes these same three discovery functions as `catalog`, `commands`, and `inspect_operation`. Group views contain only immediate child names and short descriptions. Selected operation views contain the original input/output schemas, documentation, canonical CLI argv, MCP name, and a Python attribute when it is verifiably exported by the source module. Do not reconstruct Python access from an MCP name: registered aliases and nested CLI groups need not match Python attributes.
201
+
202
+ Python uses the same bound functions:
203
+
204
+ ```python
205
+ from toolfuncs.sdk import ToolCatalog
206
+
207
+ async with ToolCatalog() as catalog:
208
+ families = catalog.catalog() # static discovery, no child startup
209
+ await catalog.start(["weather-report"])
210
+ children = catalog.commands("weather-report")
211
+ detail = catalog.inspect_operation("weather-report", ["records", "show"])
212
+ ```
213
+
214
+ In Codex Code Mode, use toolfuncs discovery to select an operation, resolve its runtime name from `ALL_TOOLS`, and invoke `tools[name](arguments)`. Retain deferred MCP exposure; native tool search is unnecessary. In Claude Code, keep the server's tools deferred (`"alwaysLoad": false` on supported versions), load the selected tool with `ToolSearch` query `select:mcp__toolfuncs__NAME`, then call it normally. Claude still exposes operation names initially. These context policies belong to the client; generic MCP clients may expose all schemas. For the agent hook's client-specific guidance, set `TOOLFUNCS_MCP_SERVER=toolfuncs` in the host environment when that server is registered.
184
215
 
185
216
  For programmatic use, `server = app.to_mcp()` returns a FastMCP server. Sync functions use FastMCP's thread dispatch and async functions are awaited. FastMCP owns request handling and cancellation. MCP calls do not share live state with an IPython kernel. The function implementation is shared.
186
217
 
187
- A nested CLI command such as `records show` becomes `records__show`; hyphens become underscores. Aliases remain CLI aliases. Ambiguous or invalid MCP names fail at startup. Every successful MCP result has structured content `{"result": value}`, including scalar and null results, plus JSON text content. Exceptions become MCP tool errors.
218
+ A nested CLI command such as `records show` becomes `records__show`; the PATH server prefixes the family, for example `weather_report__records__show`. Hyphens become underscores. Aliases remain CLI aliases. Collisions and names exceeding 64 characters fail explicitly. Every successful MCP result has structured content `{"result": value}`, including scalar and null results, plus JSON text content. Exceptions become MCP tool errors.
188
219
 
189
220
  Use stderr for logs and diagnostics. Stdout belongs to the CLI result or MCP protocol; an operation that prints to stdout can corrupt that stream. Durable background tasks, push notifications, remote hosting, resources and prompts are outside the 1.0 interface.
190
221
 
@@ -216,7 +247,7 @@ Declare `toolfuncs>=1,<2` in PEP 723 dependencies to make the required major ver
216
247
 
217
248
  The description, either `[tool.toolfuncs].description` or a package module's first docstring line, is the line agents read in `toolfuncs list` and session-start catalogs when they choose a tool. Write one sentence that says what the tool does for its caller. Keep implementation details, compatibility notes and contract caveats out of it; put them in later docstring text or in command docstrings.
218
249
 
219
- Descriptions are static by default. A tool whose available domain surface changes at runtime may opt into a last-known catalog description with `dynamic = true`:
250
+ Descriptions are static by default. A script whose available domain surface changes at runtime may opt into a last-known catalog description with `dynamic = true`:
220
251
 
221
252
  ```toml
222
253
  [tool.toolfuncs]
@@ -244,6 +275,15 @@ Only functions registered on `app` become CLI commands. Imported functions and `
244
275
 
245
276
  No `if __name__ == "__main__":` block is required. The operating system passes the source path to the shebang interpreter, and toolfuncs imports the source under its declared Python name before invoking its module-level app. Consequently, `weather-report ...` is the supported CLI while `python weather-report ...` merely defines the module and exits, or fails if its dependencies are not already installed.
246
277
 
278
+ Packaged tools opt in with an additional matching entry point:
279
+
280
+ ```toml
281
+ [project.entry-points."toolfuncs.dynamic_description"]
282
+ my-tool = "my_package.tools:app"
283
+ ```
284
+
285
+ Keep the matching `project.scripts` and `project.entry-points.toolfuncs` declarations. Publish descriptions through the same `toolsdk.publish_description` API. Discovery reads the current user's published line on each call and falls back to the module docstring when no valid line exists; it does not import the tool or access its service.
286
+
247
287
  ## Package-backed implementations
248
288
 
249
289
  A toolfunc remains one script even when most of its implementation lives in a package:
@@ -314,9 +354,12 @@ Setup does not discover, copy, register, link, synchronize, or remove tools. The
314
354
 
315
355
  ```console
316
356
  toolfuncs SOURCE [ARGS...]
317
- toolfuncs doctor SOURCE
318
- toolfuncs mcp [--prepared] SOURCE
357
+ toolfuncs doctor [--prepared] [SOURCE]
358
+ toolfuncs mcp [--prepared] [SOURCE]
319
359
  toolfuncs list
360
+ toolfuncs catalog
361
+ toolfuncs commands FAMILY [--group JSON_PATH]
362
+ toolfuncs inspect-operation FAMILY JSON_PATH
320
363
  toolfuncs setup [--bin-dir DIRECTORY]
321
364
  ```
322
365
 
@@ -8,6 +8,8 @@ Toolfuncs projects one explicitly registered set of typed Python functions into
8
8
 
9
9
  An installed distribution opts a console script into discovery by declaring the same command name and `module:app` value in its `console_scripts` and `toolfuncs` entry-point groups. Authors normally express these as `[project.scripts]` and `[project.entry-points.toolfuncs]` in `pyproject.toml`. The module contains the same module-level `app` and decorated typed functions used by script tools. There is no hand-maintained launcher: normal installers generate it.
10
10
 
11
+ A packaged tool may also declare that same name and value in the `toolfuncs.dynamic_description` entry-point group (`[project.entry-points."toolfuncs.dynamic_description"]`). Discovery then uses its valid published one-line description, with the module docstring as fallback, just as a script with `dynamic = true` does. The dynamic entry must match both ordinary declarations; it does not register an additional command. Discovery remains local and never imports the module. Published state is scoped to the current user's state directory and CLI identity, not to a package generation.
12
+
11
13
  The public executable's kebab-case name determines its snake-case facade name, with the same reserved-name checks and PATH precedence as scripts. uv-launch's versioned installation records locate the actual executable and its metadata roots, capturing a single retained generation. The actual executable must have one distribution owner across all RECORD files, a matching strong checksum, and paired console-script and toolfuncs entry points. Discovery trusts the installer to implement those entry points. It does not parse generated Python or shell bodies or authenticate package metadata. Discovery statically reads the installed module's first docstring line and requires inspectable Python source.
12
14
 
13
15
  Metadata in the current interpreter is also supported, including additive native dependency preparation. Unmanaged foreign environments are not inferred from launcher text; install those commands through uv-launch. Discovery never imports target packages, starts a subprocess, installs dependencies, or queries the network. `toolfuncs.sdk.discover(path=None)` returns the same `Catalog` as CLI listing, with `tools` and `invalid_tools`.
@@ -53,7 +55,11 @@ Cyclopts tokenizes and binds CLI arguments. Its central converter uses Pydantic'
53
55
 
54
56
  ## MCP interface
55
57
 
56
- `toolfuncs mcp SOURCE` prepares dependencies and serves one source or opted-in package executable over stdio. `--prepared` uses the current interpreter without dependency preparation. Each script is registered separately in the client's configuration. `app.to_mcp(name=...)` produces a FastMCP server for programmatic use.
58
+ `toolfuncs mcp SOURCE` prepares dependencies and serves one source or opted-in package executable over stdio. Omitting SOURCE serves the effective PATH catalog with one isolated persistent child per family. `--prepared` uses the current interpreter without dependency preparation. `app.to_mcp(name=...)` produces a FastMCP server for programmatic use.
59
+
60
+ The PATH server snapshots discovery and operation schemas at startup, retains healthy families when another fails, and records failed families in its `catalog` discovery result. Each native tool name is prefixed by its family Python name and `__`; the complete name must be unique and at most 64 characters. There is no automatic refresh, reconnect or failed-call replay. Child processes inherit the server's launch directory and environment. They are closed when the server exits.
61
+
62
+ `catalog`, `commands`, and `inspect_operation` use the same implementations in MCP, the management CLI and `toolfuncs.sdk.ToolCatalog`. Each group listing exposes immediate children only. One selected operation exposes its authoritative contract. The Python catalog is an async context manager; `await catalog.start()` loads all families, or a supplied sequence selects families. Static `sdk.discover()` and `toolfuncs list` remain import-free. Native domain calls still use the original functions through `toolfuncs.NAME`; discovery does not turn Python domain calls into RPC.
57
63
 
58
64
  Canonical command paths become tool names by replacing hyphens with underscores and joining path segments with `__`. A root default operation uses its function name. Names must contain 1-64 ASCII letters, digits, underscores or hyphens. CLI aliases are deduplicated; name collisions fail.
59
65
 
@@ -116,7 +122,7 @@ Successful default CLI operations render one Python value through Rich on an int
116
122
 
117
123
  Setup does not discover, install, copy, link, register, synchronize, or remove tool sources. Adding and removing tools are ordinary filesystem operations in directories already on `PATH`.
118
124
 
119
- The public management commands are `toolfuncs mcp [--prepared] SOURCE`, `toolfuncs doctor SOURCE`, `toolfuncs list`, and `toolfuncs setup [--bin-dir DIRECTORY]`. The explicit-source execution form is `toolfuncs SOURCE [ARGS...]`; it is not a universal name dispatcher. Tools on `PATH` are invoked directly by their own CLI names. There is no `run`, `exec`, `describe`, or `sync` command.
125
+ The public management commands include `toolfuncs mcp [--prepared] [SOURCE]`, `toolfuncs doctor [--prepared] [SOURCE]`, `toolfuncs list`, `toolfuncs catalog`, `toolfuncs commands FAMILY [--group JSON_PATH]`, `toolfuncs inspect-operation FAMILY JSON_PATH`, and `toolfuncs setup [--bin-dir DIRECTORY]`. Doctor without SOURCE checks aggregate loading and name validity; doctor with SOURCE also checks its authoring documentation and generated help. The explicit-source execution form is `toolfuncs SOURCE [ARGS...]`; it is not a universal name dispatcher. Tools on `PATH` are invoked directly by their own CLI names. There is no generic operation-call command.
120
126
 
121
127
  ## Compatibility
122
128
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "toolfuncs"
3
- version = "1.0.0"
3
+ version = "1.2.0"
4
4
  description = "Typed Python functions with native Python, CLI, and MCP interfaces"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  import json
6
7
  import sys
7
8
  from collections.abc import Iterator, Sequence
@@ -31,9 +32,12 @@ Usage:
31
32
  toolfuncs --version
32
33
  toolfuncs SOURCE [ARGS...]
33
34
  toolfuncs run-prepared SOURCE [ARGS...]
34
- toolfuncs doctor [--prepared] SOURCE
35
- toolfuncs mcp [--prepared] SOURCE
35
+ toolfuncs doctor [--prepared] [SOURCE]
36
+ toolfuncs mcp [--prepared] [SOURCE]
36
37
  toolfuncs list
38
+ toolfuncs catalog
39
+ toolfuncs commands FAMILY [--group JSON_PATH]
40
+ toolfuncs inspect-operation FAMILY JSON_PATH
37
41
  toolfuncs setup [--bin-dir DIRECTORY]
38
42
 
39
43
  A toolfunc is an executable Python script whose first line is exactly:
@@ -53,8 +57,12 @@ run-prepared and doctor --prepared use the current interpreter without resolving
53
57
  or installing dependencies. Installers must prepare the script's declared runtime
54
58
  first. They still validate the source and its Python-version requirement.
55
59
 
56
- mcp serves one selected script or installed package command over stdio. Register
57
- this command explicitly with your MCP client. No PATH aggregation or live reload.
60
+ mcp serves native tools from PATH over stdio, or one selected SOURCE. Register it
61
+ once with your client. Each family runs in its own persistent process. Restart
62
+ the server after installing or changing tools; there is no automatic reload.
63
+ Discovery proceeds from catalog to commands to inspect-operation. These commands
64
+ share their implementation with the Python ToolCatalog and native MCP discovery.
65
+ doctor without SOURCE checks all discovered families and reports partial failures.
58
66
  """
59
67
 
60
68
 
@@ -108,11 +116,27 @@ def main(argv: Sequence[str] | None = None) -> int:
108
116
  _print_json(_catalog_payload(catalog))
109
117
  _warn_invalid_tools(catalog.invalid_tools)
110
118
  return 0
119
+ if command in {"catalog", "commands", "inspect-operation"}:
120
+ from ._catalog import run_discovery
121
+
122
+ return asyncio.run(run_discovery(command, tokens))
111
123
  if command in {"doctor", "__doctor-source"}:
112
124
  prepared = command == "__doctor-source"
113
125
  if command == "doctor" and tokens[:1] == ["--prepared"]:
114
126
  prepared = True
115
127
  tokens.pop(0)
128
+ if not tokens and command == "doctor":
129
+ from ._catalog import doctor_catalog
130
+
131
+ families = asyncio.run(doctor_catalog(prepared=prepared))
132
+ healthy = all(family.status == "ready" for family in families)
133
+ _print_json(
134
+ {
135
+ "status": "success" if healthy else "error",
136
+ "families": [family.model_dump(mode="json") for family in families],
137
+ }
138
+ )
139
+ return 0 if healthy else 1
116
140
  if len(tokens) != 1:
117
141
  raise SystemExit("usage: toolfuncs doctor SOURCE")
118
142
  if not prepared and Path(tokens[0]).is_file():
@@ -136,6 +160,11 @@ def main(argv: Sequence[str] | None = None) -> int:
136
160
  if tokens[:1] == ["--prepared"]:
137
161
  prepared = True
138
162
  tokens.pop(0)
163
+ if not tokens and command == "mcp":
164
+ from ._catalog import create_path_server
165
+
166
+ create_path_server(prepared=prepared).run(transport="stdio", show_banner=False)
167
+ return 0
139
168
  if len(tokens) != 1:
140
169
  raise SystemExit("usage: toolfuncs mcp [--prepared] SOURCE")
141
170
  source = Path(tokens[0])
@@ -144,7 +173,10 @@ def main(argv: Sequence[str] | None = None) -> int:
144
173
  raise AssertionError("exec returned")
145
174
  # Import diagnostics must not be mistaken for JSON-RPC protocol frames.
146
175
  with redirect_stdout(sys.stderr), _tool_program_name(source):
147
- server = _application(_load_prepared_source(source)).to_mcp(name=source.name)
176
+ from ._mcp import create_server
177
+
178
+ module = _load_prepared_source(source)
179
+ server = create_server(_application(module), name=source.name, source_module=module)
148
180
  server.run(transport="stdio", show_banner=False)
149
181
  return 0
150
182
  if command == "setup":