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.
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/PKG-INFO +121 -3
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/README.md +117 -1
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/pyproject.toml +12 -2
- toolgovern_cli-0.1.4/src/toolgovern/mcp_server.py +129 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/.gitignore +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/LICENSE +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/approval/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/approval/pending_registry.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/credential_access.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/cross_agent_inheritance.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/filesystem_scope.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/index.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/information_flow.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/network_egress.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/shell_risk.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/util.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/cli.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/mcp_trust/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/middleware/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/middleware/idempotency_cache.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/middleware/on_tool_call.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/policy/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/policy/load_policy.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/policy/validate_policy.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/py.typed +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/scoping/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/scoping/inheritance_enforcer.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/scoping/scope_declaration.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/shared/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/shared/paths.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/canonical_json.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/trace_reader.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/trace/trace_writer.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/types.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/__init__.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/conftest.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_credential_access.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_cross_agent_inheritance.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_filesystem_scope.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_index.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_information_flow.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_network_egress.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_shell_risk.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_cli.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_mcp_trust.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_middleware_on_tool_call.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_pending_registry.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_policy.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_scoping_inheritance_enforcer.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_scoping_scope_declaration.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_trace_canonical_json.py +0 -0
- {toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_trace_reader.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/credential_access.py
RENAMED
|
File without changes
|
{toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/classifier/cross_agent_inheritance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/middleware/idempotency_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/src/toolgovern/scoping/inheritance_enforcer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{toolgovern_cli-0.1.2 → toolgovern_cli-0.1.4}/tests/test_classifier_cross_agent_inheritance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|