toolgovern-cli 0.1.2__tar.gz → 0.1.4__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 (56) hide show
  1. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/PKG-INFO +121 -3
  2. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/README.md +117 -1
  3. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/pyproject.toml +12 -2
  4. toolgovern_cli-0.1.4/src/toolgovern/mcp_server.py +129 -0
  5. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/.gitignore +0 -0
  6. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/LICENSE +0 -0
  7. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/__init__.py +0 -0
  8. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/approval/__init__.py +0 -0
  9. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/approval/pending_registry.py +0 -0
  10. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/__init__.py +0 -0
  11. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/credential_access.py +0 -0
  12. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/cross_agent_inheritance.py +0 -0
  13. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/filesystem_scope.py +0 -0
  14. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/index.py +0 -0
  15. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/information_flow.py +0 -0
  16. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/network_egress.py +0 -0
  17. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/shell_risk.py +0 -0
  18. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/util.py +0 -0
  19. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/cli.py +0 -0
  20. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/mcp_trust/__init__.py +0 -0
  21. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/middleware/__init__.py +0 -0
  22. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/middleware/idempotency_cache.py +0 -0
  23. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/middleware/on_tool_call.py +0 -0
  24. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/policy/__init__.py +0 -0
  25. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/policy/load_policy.py +0 -0
  26. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/policy/validate_policy.py +0 -0
  27. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/py.typed +0 -0
  28. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/scoping/__init__.py +0 -0
  29. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/scoping/inheritance_enforcer.py +0 -0
  30. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/scoping/scope_declaration.py +0 -0
  31. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/shared/__init__.py +0 -0
  32. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/shared/paths.py +0 -0
  33. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/__init__.py +0 -0
  34. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/canonical_json.py +0 -0
  35. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/trace_reader.py +0 -0
  36. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/trace_writer.py +0 -0
  37. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/types.py +0 -0
  38. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/__init__.py +0 -0
  39. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/conftest.py +0 -0
  40. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_credential_access.py +0 -0
  41. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_cross_agent_inheritance.py +0 -0
  42. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_filesystem_scope.py +0 -0
  43. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_index.py +0 -0
  44. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_information_flow.py +0 -0
  45. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_network_egress.py +0 -0
  46. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_shell_risk.py +0 -0
  47. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_cli.py +0 -0
  48. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_mcp_trust.py +0 -0
  49. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_middleware_on_tool_call.py +0 -0
  50. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_pending_registry.py +0 -0
  51. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_policy.py +0 -0
  52. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_scoping_inheritance_enforcer.py +0 -0
  53. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_scoping_scope_declaration.py +0 -0
  54. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_trace_canonical_json.py +0 -0
  55. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_trace_reader.py +0 -0
  56. {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_trace_writer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolgovern-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Runtime governance middleware for AI agent tool calls -- gate shell, filesystem, network, and credential access before a tool executes.
5
5
  Project-URL: Homepage, https://github.com/RudrenduPaul/toolgovern
6
6
  Project-URL: Repository, https://github.com/RudrenduPaul/toolgovern
@@ -12,7 +12,7 @@ Project-URL: Author - Sourav Nandy, https://github.com/Sourav-nandy-ai
12
12
  Author: Rudrendu Paul, Sourav Nandy
13
13
  License-Expression: Apache-2.0
14
14
  License-File: LICENSE
15
- Keywords: agent-governance,ai-agents,audit-trail,cli,mcp,runtime-security,security,tool-calling
15
+ Keywords: agent-governance,ai-agents,audit-trail,cli,mcp,policy-enforcement,runtime-security,scope-narrowing,security,tool-calling
16
16
  Classifier: Development Status :: 3 - Alpha
17
17
  Classifier: Environment :: Console
18
18
  Classifier: Intended Audience :: Developers
@@ -33,8 +33,11 @@ Provides-Extra: dev
33
33
  Requires-Dist: build<2,>=1.0; extra == 'dev'
34
34
  Requires-Dist: pytest<10,>=9.0.3; extra == 'dev'
35
35
  Requires-Dist: twine<7,>=5.0; extra == 'dev'
36
+ Provides-Extra: mcp
37
+ Requires-Dist: mcp[cli]>=2.0.0; extra == 'mcp'
36
38
  Description-Content-Type: text/markdown
37
39
 
40
+ <!-- mcp-name: io.github.RudrenduPaul/toolgovern -->
38
41
  # toolgovern (Python)
39
42
 
40
43
  Gate every tool call an AI agent makes -- shell, filesystem, network, credential access -- before
@@ -259,6 +262,35 @@ from toolgovern import (
259
262
  )
260
263
  ```
261
264
 
265
+ ## How it compares to other agent governance projects
266
+
267
+ Same facts as the [project README's full comparison
268
+ table](https://github.com/RudrenduPaul/toolgovern#how-it-compares-to-other-agent-governance-projects)
269
+ -- condensed here to the rows that matter most for picking a package, not re-derived. The "Rules
270
+ out of the box" row below is 36, not 35, because this Python port folds the DNS-resolution check
271
+ (`TG03-dns-resolves-private`) directly into its one synchronous `classify()` instead of needing a
272
+ separate async entry point -- see [What it does](#what-it-does) above. Every other row applies
273
+ equally to both the TypeScript and Python distributions.
274
+
275
+ | | **toolgovern** | [Microsoft Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit) | [NVIDIA NeMo Relay](https://github.com/NVIDIA/NeMo-Relay) | [LangGraph human-in-the-loop](https://docs.langchain.com/oss/python/langchain/human-in-the-loop) |
276
+ | -------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
277
+ | What it actually gates | Tool calls, pre-execution, against a built-in rule set | Tool calls, messages, and delegation, pre-execution, against policy you author (YAML/OPA/Cedar) | Tool and LLM calls via pre-tool hooks -- coverage depends on the host agent | A single tool call, paused for a human decision -- no automated risk classification |
278
+ | Rules out of the box | 36 (this Python port), across 6 categories, zero config | None shipped -- you write the policy | None shipped -- pre-tool hooks call your own logic, not a built-in classifier | None -- you decide per call |
279
+ | Per-agent scope narrowing | Yes -- a sub-agent can never exceed its coordinator's granted scope | Yes -- documented delegation-chain narrowing and a 4-ring privilege model | Not publicly documented | No |
280
+ | Tamper-evident audit trail | Yes -- signed, hash-chained local JSONL | Yes -- Merkle-audit-backed, 157 conformance tests just for the audit layer | No -- raw JSONL trajectory export (ATOF/ATIF format), not signed | No |
281
+ | Hosted component required | No, never | No -- self-hosted by design, Azure integration is optional | No -- local CLI gateway | No for the OSS library; LangGraph's own hosted server runtime is separately licensed |
282
+ | License | Apache 2.0 | MIT | Apache 2.0 | MIT |
283
+
284
+ Two things worth repeating from the full narrative rather than leaving implicit: Microsoft's
285
+ Agent Governance Toolkit already matches or exceeds this project on scoping and audit-trail
286
+ maturity (a formal delegation-chain spec, 157 conformance tests just for its audit layer) -- this
287
+ table is not a claim that toolgovern beats AGT. And NeMo Relay / LangGraph HITL are doing a
288
+ genuinely different job, not a weaker version of the same one -- listing them here is about scope,
289
+ not a claim of superiority at the task each of them is actually built for. Read the [full
290
+ comparison and both honest
291
+ caveats](https://github.com/RudrenduPaul/toolgovern#how-it-compares-to-other-agent-governance-projects)
292
+ in the project README before deciding what you need.
293
+
262
294
  ## CLI
263
295
 
264
296
  ```bash
@@ -273,6 +305,43 @@ structured-output envelope (`{ ok, command, data | error }`). **Not ported in th
273
305
  it generates a `.ts` file importing the JS/TS-only `toolgovern-integration-langgraph` /
274
306
  `toolgovern-integration-oma` packages, which are out of scope for a Python port by nature.
275
307
 
308
+ ## MCP Server
309
+
310
+ toolgovern-cli ships a Model Context Protocol server, so an MCP-compatible agent (Claude
311
+ Desktop, Claude Code, or any other MCP client) can call `validate` and `audit` directly instead
312
+ of shelling out and parsing text.
313
+
314
+ ```bash
315
+ pip install "toolgovern-cli[mcp]"
316
+ ```
317
+
318
+ Claude Desktop config (`claude_desktop_config.json`):
319
+
320
+ ```json
321
+ {
322
+ "mcpServers": {
323
+ "toolgovern": {
324
+ "command": "toolgovern-mcp"
325
+ }
326
+ }
327
+ }
328
+ ```
329
+
330
+ The server exposes one tool, `run`, which takes the same argument list you'd pass to
331
+ `toolgovern-cli` on the command line and returns its result as structured JSON -- it never
332
+ raises, even on a bad file, a timeout, or non-JSON output; every failure comes back as
333
+ `{"error": ...}` instead:
334
+
335
+ ```
336
+ run(args=["validate", "./toolgovern.policy.yml", "--json"])
337
+ ```
338
+
339
+ This is a generic subprocess wrapper around the real CLI (not a second implementation of each
340
+ subcommand), so it stays in sync with `validate`, `audit`, and any future subcommand
341
+ automatically. This is distinct from toolgovern's `mcp_trust` module, which is a client-side
342
+ tool for verifying the trustworthiness of *other* MCP servers an agent connects to -- this
343
+ section is about toolgovern-cli exposing its own MCP server for agents to call.
344
+
276
345
  ## The signed audit trail
277
346
 
278
347
  ```python
@@ -322,6 +391,56 @@ pytest
322
391
 
323
392
  Report a vulnerability per the project's [`SECURITY.md`](https://github.com/RudrenduPaul/toolgovern/blob/main/SECURITY.md); please don't open a public issue for one.
324
393
 
394
+ ## FAQ
395
+
396
+ **What does toolgovern do?**
397
+ It's a runtime gate that checks every tool call an AI agent makes -- shell, filesystem, network,
398
+ credential access -- against a 36-rule classifier before the call executes, not after.
399
+ `govern_tool()` wraps any `ToolDefinition(name, execute)` you already have and runs each call
400
+ through the classifier, the per-agent scope registry, and (if wired in) the signed trace writer
401
+ before your real `execute()` ever fires. See [Why this exists](#why-this-exists) and [What it
402
+ does](#what-it-does) above for the full case.
403
+
404
+ **How does this Python package differ from the npm package, if at all?**
405
+ Functionally, barely. It ships the same 36-rule classifier (the npm/TypeScript package runs 35
406
+ rules synchronously plus one additional async-only DNS-resolution rule, landing at 36 checks total
407
+ through its `classifyAsync()` path; this Python port folds that same DNS check into its one
408
+ synchronous `classify()` instead, so it's 36 either way), the same intersection-only scope
409
+ registry, the same durable approval registry, the same MCP-server trust boundary, and the same
410
+ signed trace format -- a genuine Python port, not a wrapper around the Node binary. Two real gaps
411
+ today: `toolgovern-cli init [oma|langgraph]` (the npm CLI's TypeScript integration-file scaffolder)
412
+ isn't ported, since it generates a `.ts` file importing JS/TS-only packages; and the two npm-only
413
+ integration packages (`toolgovern-integration-oma`, `toolgovern-integration-langgraph` for
414
+ LangGraph.js) have no Python equivalent by design -- wire `govern_tool()` directly into your
415
+ Python framework's own call site instead. See [CLI](#cli) and [Framework
416
+ integrations](#framework-integrations) above.
417
+
418
+ **Does it need API keys or an account?**
419
+ No. Nothing in this package calls out to a hosted service. No call payload, argument, trace
420
+ content, or policy leaves your process unless code you write sends it somewhere -- there's no
421
+ server dependency, no account, and nothing to sign up for.
422
+
423
+ **Is it safe to run -- does an `allow` decision mean a tool call is safe?**
424
+ Running the package itself is safe: it's a local, in-process classifier that makes no network
425
+ calls of its own (the one exception, `TG03-dns-resolves-private`, only performs a DNS lookup of an
426
+ argument value your own tool call passes it). But an `allow` decision is not a safety guarantee --
427
+ it means the call was checked against the current 36-rule set and nothing fired.
428
+ [`docs/security-model.md`](https://github.com/RudrenduPaul/toolgovern/blob/main/docs/security-model.md)
429
+ in the main repo documents exactly what the classifier does and doesn't catch, including disclosed
430
+ obfuscation techniques it can still miss.
431
+
432
+ **How do I use it from an agent?**
433
+ Five real Python framework integrations exist in the main repo -- LangGraph (using the real
434
+ `wrap_tool_call` `ToolNode` parameter), CrewAI, AutoGen, Microsoft Agent Framework, and the Claude
435
+ Agent SDK (using its real `PreToolUse` hook) -- each installable from source (none are published to
436
+ PyPI yet). For a framework without a dedicated integration, wrap your own tool definitions with
437
+ `govern_tool()` directly at whatever call site your framework dispatches tool calls from. See
438
+ [Framework integrations](#framework-integrations) above for install commands and worked examples.
439
+
440
+ **Is there a hosted version of toolgovern?**
441
+ No. Everything that exists today is in the GitHub repository, Apache 2.0, self-hosted only, for
442
+ both the Python and TypeScript distributions.
443
+
325
444
  ## Links
326
445
 
327
446
  - [GitHub repository](https://github.com/RudrenduPaul/toolgovern)
@@ -335,4 +454,3 @@ Report a vulnerability per the project's [`SECURITY.md`](https://github.com/Rudr
335
454
  ## License
336
455
 
337
456
  Apache 2.0 -- see [LICENSE](../LICENSE).
338
-
@@ -1,3 +1,4 @@
1
+ <!-- mcp-name: io.github.RudrenduPaul/toolgovern -->
1
2
  # toolgovern (Python)
2
3
 
3
4
  Gate every tool call an AI agent makes -- shell, filesystem, network, credential access -- before
@@ -222,6 +223,35 @@ from toolgovern import (
222
223
  )
223
224
  ```
224
225
 
226
+ ## How it compares to other agent governance projects
227
+
228
+ Same facts as the [project README's full comparison
229
+ table](https://github.com/RudrenduPaul/toolgovern#how-it-compares-to-other-agent-governance-projects)
230
+ -- condensed here to the rows that matter most for picking a package, not re-derived. The "Rules
231
+ out of the box" row below is 36, not 35, because this Python port folds the DNS-resolution check
232
+ (`TG03-dns-resolves-private`) directly into its one synchronous `classify()` instead of needing a
233
+ separate async entry point -- see [What it does](#what-it-does) above. Every other row applies
234
+ equally to both the TypeScript and Python distributions.
235
+
236
+ | | **toolgovern** | [Microsoft Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit) | [NVIDIA NeMo Relay](https://github.com/NVIDIA/NeMo-Relay) | [LangGraph human-in-the-loop](https://docs.langchain.com/oss/python/langchain/human-in-the-loop) |
237
+ | -------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
238
+ | What it actually gates | Tool calls, pre-execution, against a built-in rule set | Tool calls, messages, and delegation, pre-execution, against policy you author (YAML/OPA/Cedar) | Tool and LLM calls via pre-tool hooks -- coverage depends on the host agent | A single tool call, paused for a human decision -- no automated risk classification |
239
+ | Rules out of the box | 36 (this Python port), across 6 categories, zero config | None shipped -- you write the policy | None shipped -- pre-tool hooks call your own logic, not a built-in classifier | None -- you decide per call |
240
+ | Per-agent scope narrowing | Yes -- a sub-agent can never exceed its coordinator's granted scope | Yes -- documented delegation-chain narrowing and a 4-ring privilege model | Not publicly documented | No |
241
+ | Tamper-evident audit trail | Yes -- signed, hash-chained local JSONL | Yes -- Merkle-audit-backed, 157 conformance tests just for the audit layer | No -- raw JSONL trajectory export (ATOF/ATIF format), not signed | No |
242
+ | Hosted component required | No, never | No -- self-hosted by design, Azure integration is optional | No -- local CLI gateway | No for the OSS library; LangGraph's own hosted server runtime is separately licensed |
243
+ | License | Apache 2.0 | MIT | Apache 2.0 | MIT |
244
+
245
+ Two things worth repeating from the full narrative rather than leaving implicit: Microsoft's
246
+ Agent Governance Toolkit already matches or exceeds this project on scoping and audit-trail
247
+ maturity (a formal delegation-chain spec, 157 conformance tests just for its audit layer) -- this
248
+ table is not a claim that toolgovern beats AGT. And NeMo Relay / LangGraph HITL are doing a
249
+ genuinely different job, not a weaker version of the same one -- listing them here is about scope,
250
+ not a claim of superiority at the task each of them is actually built for. Read the [full
251
+ comparison and both honest
252
+ caveats](https://github.com/RudrenduPaul/toolgovern#how-it-compares-to-other-agent-governance-projects)
253
+ in the project README before deciding what you need.
254
+
225
255
  ## CLI
226
256
 
227
257
  ```bash
@@ -236,6 +266,43 @@ structured-output envelope (`{ ok, command, data | error }`). **Not ported in th
236
266
  it generates a `.ts` file importing the JS/TS-only `toolgovern-integration-langgraph` /
237
267
  `toolgovern-integration-oma` packages, which are out of scope for a Python port by nature.
238
268
 
269
+ ## MCP Server
270
+
271
+ toolgovern-cli ships a Model Context Protocol server, so an MCP-compatible agent (Claude
272
+ Desktop, Claude Code, or any other MCP client) can call `validate` and `audit` directly instead
273
+ of shelling out and parsing text.
274
+
275
+ ```bash
276
+ pip install "toolgovern-cli[mcp]"
277
+ ```
278
+
279
+ Claude Desktop config (`claude_desktop_config.json`):
280
+
281
+ ```json
282
+ {
283
+ "mcpServers": {
284
+ "toolgovern": {
285
+ "command": "toolgovern-mcp"
286
+ }
287
+ }
288
+ }
289
+ ```
290
+
291
+ The server exposes one tool, `run`, which takes the same argument list you'd pass to
292
+ `toolgovern-cli` on the command line and returns its result as structured JSON -- it never
293
+ raises, even on a bad file, a timeout, or non-JSON output; every failure comes back as
294
+ `{"error": ...}` instead:
295
+
296
+ ```
297
+ run(args=["validate", "./toolgovern.policy.yml", "--json"])
298
+ ```
299
+
300
+ This is a generic subprocess wrapper around the real CLI (not a second implementation of each
301
+ subcommand), so it stays in sync with `validate`, `audit`, and any future subcommand
302
+ automatically. This is distinct from toolgovern's `mcp_trust` module, which is a client-side
303
+ tool for verifying the trustworthiness of *other* MCP servers an agent connects to -- this
304
+ section is about toolgovern-cli exposing its own MCP server for agents to call.
305
+
239
306
  ## The signed audit trail
240
307
 
241
308
  ```python
@@ -285,6 +352,56 @@ pytest
285
352
 
286
353
  Report a vulnerability per the project's [`SECURITY.md`](https://github.com/RudrenduPaul/toolgovern/blob/main/SECURITY.md); please don't open a public issue for one.
287
354
 
355
+ ## FAQ
356
+
357
+ **What does toolgovern do?**
358
+ It's a runtime gate that checks every tool call an AI agent makes -- shell, filesystem, network,
359
+ credential access -- against a 36-rule classifier before the call executes, not after.
360
+ `govern_tool()` wraps any `ToolDefinition(name, execute)` you already have and runs each call
361
+ through the classifier, the per-agent scope registry, and (if wired in) the signed trace writer
362
+ before your real `execute()` ever fires. See [Why this exists](#why-this-exists) and [What it
363
+ does](#what-it-does) above for the full case.
364
+
365
+ **How does this Python package differ from the npm package, if at all?**
366
+ Functionally, barely. It ships the same 36-rule classifier (the npm/TypeScript package runs 35
367
+ rules synchronously plus one additional async-only DNS-resolution rule, landing at 36 checks total
368
+ through its `classifyAsync()` path; this Python port folds that same DNS check into its one
369
+ synchronous `classify()` instead, so it's 36 either way), the same intersection-only scope
370
+ registry, the same durable approval registry, the same MCP-server trust boundary, and the same
371
+ signed trace format -- a genuine Python port, not a wrapper around the Node binary. Two real gaps
372
+ today: `toolgovern-cli init [oma|langgraph]` (the npm CLI's TypeScript integration-file scaffolder)
373
+ isn't ported, since it generates a `.ts` file importing JS/TS-only packages; and the two npm-only
374
+ integration packages (`toolgovern-integration-oma`, `toolgovern-integration-langgraph` for
375
+ LangGraph.js) have no Python equivalent by design -- wire `govern_tool()` directly into your
376
+ Python framework's own call site instead. See [CLI](#cli) and [Framework
377
+ integrations](#framework-integrations) above.
378
+
379
+ **Does it need API keys or an account?**
380
+ No. Nothing in this package calls out to a hosted service. No call payload, argument, trace
381
+ content, or policy leaves your process unless code you write sends it somewhere -- there's no
382
+ server dependency, no account, and nothing to sign up for.
383
+
384
+ **Is it safe to run -- does an `allow` decision mean a tool call is safe?**
385
+ Running the package itself is safe: it's a local, in-process classifier that makes no network
386
+ calls of its own (the one exception, `TG03-dns-resolves-private`, only performs a DNS lookup of an
387
+ argument value your own tool call passes it). But an `allow` decision is not a safety guarantee --
388
+ it means the call was checked against the current 36-rule set and nothing fired.
389
+ [`docs/security-model.md`](https://github.com/RudrenduPaul/toolgovern/blob/main/docs/security-model.md)
390
+ in the main repo documents exactly what the classifier does and doesn't catch, including disclosed
391
+ obfuscation techniques it can still miss.
392
+
393
+ **How do I use it from an agent?**
394
+ Five real Python framework integrations exist in the main repo -- LangGraph (using the real
395
+ `wrap_tool_call` `ToolNode` parameter), CrewAI, AutoGen, Microsoft Agent Framework, and the Claude
396
+ Agent SDK (using its real `PreToolUse` hook) -- each installable from source (none are published to
397
+ PyPI yet). For a framework without a dedicated integration, wrap your own tool definitions with
398
+ `govern_tool()` directly at whatever call site your framework dispatches tool calls from. See
399
+ [Framework integrations](#framework-integrations) above for install commands and worked examples.
400
+
401
+ **Is there a hosted version of toolgovern?**
402
+ No. Everything that exists today is in the GitHub repository, Apache 2.0, self-hosted only, for
403
+ both the Python and TypeScript distributions.
404
+
288
405
  ## Links
289
406
 
290
407
  - [GitHub repository](https://github.com/RudrenduPaul/toolgovern)
@@ -298,4 +415,3 @@ Report a vulnerability per the project's [`SECURITY.md`](https://github.com/Rudr
298
415
  ## License
299
416
 
300
417
  Apache 2.0 -- see [LICENSE](../LICENSE).
301
-
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "toolgovern-cli"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "Runtime governance middleware for AI agent tool calls -- gate shell, filesystem, network, and credential access before a tool executes."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -13,7 +13,7 @@ authors = [
13
13
  { name = "Rudrendu Paul" },
14
14
  { name = "Sourav Nandy" },
15
15
  ]
16
- keywords = ["security", "ai-agents", "agent-governance", "tool-calling", "runtime-security", "audit-trail", "cli", "mcp"]
16
+ keywords = ["security", "ai-agents", "agent-governance", "tool-calling", "runtime-security", "audit-trail", "cli", "mcp", "policy-enforcement", "scope-narrowing"]
17
17
  classifiers = [
18
18
  "Development Status :: 3 - Alpha",
19
19
  "Environment :: Console",
@@ -40,6 +40,12 @@ dev = [
40
40
  "build>=1.0,<2",
41
41
  "twine>=5.0,<7",
42
42
  ]
43
+ # Pinned to >=2.0.0: mcp_server.py uses `mcp.server.MCPServer`, the current
44
+ # high-level server class -- `mcp.server.fastmcp.FastMCP` was removed in
45
+ # the 2.0.0 release.
46
+ mcp = [
47
+ "mcp[cli]>=2.0.0",
48
+ ]
43
49
 
44
50
  [project.urls]
45
51
  Homepage = "https://github.com/RudrenduPaul/toolgovern"
@@ -52,6 +58,10 @@ Documentation = "https://github.com/RudrenduPaul/toolgovern/blob/main/docs/getti
52
58
 
53
59
  [project.scripts]
54
60
  toolgovern-cli = "toolgovern.cli:main"
61
+ toolgovern-mcp = "toolgovern.mcp_server:main"
62
+
63
+ [tool.hatch.build]
64
+ exclude = [".venv*/"]
55
65
 
56
66
  [tool.hatch.build.targets.wheel]
57
67
  packages = ["src/toolgovern"]
@@ -0,0 +1,129 @@
1
+ """MCP server (Python): exposes the toolgovern-cli command-line tool to agent
2
+ runtimes over stdio.
3
+
4
+ Requires the `mcp` extra (`pip install "toolgovern-cli[mcp]"`). Started via
5
+ the `toolgovern-mcp` console script (installed by `python/pyproject.toml`'s
6
+ `[project.scripts]`).
7
+
8
+ This is a generic subprocess wrapper, not a per-subcommand tool set: a
9
+ single `run` tool shells out to `python -m toolgovern.cli <args>` (invoked by
10
+ module rather than by looking up the `toolgovern-cli` binary on PATH, so it
11
+ works the same whether or not the console script entry point is installed)
12
+ and returns the result. Wrapping the CLI this way means the tool stays in
13
+ sync with `validate`, `audit`, and any future subcommand without a matching
14
+ MCP tool hand-written for each one.
15
+
16
+ Every failure path (the subprocess never starting, timing out, exiting
17
+ non-zero, or printing non-JSON stdout) is caught and returned as a
18
+ `{"error": ...}` dict. This tool handler must never raise -- an uncaught
19
+ exception here would surface as a raw MCP protocol error instead of a
20
+ readable result.
21
+
22
+ Uses `mcp.server.MCPServer`, the official SDK's current high-level server
23
+ class (`mcp` 2.0.0+) -- earlier `mcp` 1.x releases exposed the same
24
+ `.tool()`/`.run()` pattern under `mcp.server.fastmcp.FastMCP`, which was
25
+ removed in the 2.0.0 release.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import json
31
+ import subprocess
32
+ import sys
33
+ from typing import Any
34
+
35
+ from mcp.server import MCPServer
36
+
37
+ _TIMEOUT_SECONDS = 60
38
+
39
+ _STATIC_FALLBACK_DESCRIPTION = (
40
+ "Run the toolgovern-cli command-line tool with the given argument list "
41
+ "and return its output. toolgovern-cli validates governance policy "
42
+ "files and audits signed local trace logs of allow/deny/require-"
43
+ "approval decisions made by toolgovern's runtime tool-call gate. Pass "
44
+ "the same arguments you would give the `toolgovern-cli` command on the "
45
+ 'command line, e.g. run(args=["validate", "./toolgovern.policy.yml", '
46
+ '"--json"]).'
47
+ )
48
+
49
+
50
+ def _get_cli_help() -> str:
51
+ """Runs `python -m toolgovern.cli --help` to source the tool
52
+ description from the CLI's real, current `--help` text. Returns "" on
53
+ any failure so the caller can fall back to the static description
54
+ instead of crashing at import time."""
55
+ try:
56
+ result = subprocess.run(
57
+ [sys.executable, "-m", "toolgovern.cli", "--help"],
58
+ capture_output=True,
59
+ text=True,
60
+ timeout=_TIMEOUT_SECONDS,
61
+ )
62
+ except (OSError, subprocess.TimeoutExpired):
63
+ return ""
64
+ return (result.stdout or result.stderr).strip()
65
+
66
+
67
+ def _build_run_description() -> str:
68
+ help_text = _get_cli_help()
69
+ if not help_text:
70
+ return _STATIC_FALLBACK_DESCRIPTION
71
+ return (
72
+ "Run the toolgovern-cli command-line tool with the given argument "
73
+ f"list and return its output.\n\n{help_text}"
74
+ )
75
+
76
+
77
+ # Populated once at import time from the real, installed CLI -- not
78
+ # hand-maintained, so it can't silently drift from actual `--help` output.
79
+ _RUN_TOOL_DESCRIPTION = _build_run_description()
80
+
81
+
82
+ def build_app() -> MCPServer:
83
+ app = MCPServer("toolgovern")
84
+
85
+ @app.tool(description=_RUN_TOOL_DESCRIPTION)
86
+ def run(args: list[str]) -> dict[str, Any]:
87
+ try:
88
+ result = subprocess.run(
89
+ [sys.executable, "-m", "toolgovern.cli", *args],
90
+ capture_output=True,
91
+ text=True,
92
+ timeout=_TIMEOUT_SECONDS,
93
+ )
94
+ except OSError as error:
95
+ return {"error": f"failed to launch the toolgovern-cli CLI: {error}"}
96
+ except subprocess.TimeoutExpired:
97
+ return {"error": f"toolgovern-cli timed out after {_TIMEOUT_SECONDS}s"}
98
+
99
+ stdout = result.stdout.strip()
100
+ stderr = result.stderr.strip()
101
+
102
+ if result.returncode != 0:
103
+ return {
104
+ "error": stderr or stdout or f"toolgovern-cli exited with code {result.returncode}",
105
+ "returncode": result.returncode,
106
+ }
107
+
108
+ if not stdout:
109
+ return {"returncode": result.returncode, "stdout": "", "stderr": stderr}
110
+
111
+ try:
112
+ return {"result": json.loads(stdout)}
113
+ except json.JSONDecodeError:
114
+ # Not every subcommand supports --json (or the caller didn't
115
+ # pass it) -- return the raw text rather than treating this as
116
+ # an error.
117
+ return {"returncode": result.returncode, "stdout": stdout, "stderr": stderr}
118
+
119
+ return app
120
+
121
+
122
+ def main() -> None:
123
+ """Entry point for the `toolgovern-mcp` console script."""
124
+ app = build_app()
125
+ app.run(transport="stdio")
126
+
127
+
128
+ if __name__ == "__main__":
129
+ main()
File without changes