# https://github.com/Node804/node804-mcp-toolkit Project Manual

Generated at: 2026-06-21 02:49:13 UTC

## Table of Contents

- [Toolkit Overview and Design Philosophy](#page-overview)
- [Permission Gating with ModeGate](#page-rbac)
- [Audit Logging and Argument Sanitization](#page-audit)
- [Response Shaping and Shared Parameter Schemas](#page-lean-params)

<a id='page-overview'></a>

## Toolkit Overview and Design Philosophy

### Related Pages

Related topics: [Permission Gating with ModeGate](#page-rbac), [Audit Logging and Argument Sanitization](#page-audit), [Response Shaping and Shared Parameter Schemas](#page-lean-params)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md)
- [src/node804_mcp_toolkit/__init__.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/__init__.py)
- [src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py)
- [src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py)
- [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py)
- [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py)
- [src/node804_mcp_toolkit/tls.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/tls.py)
</details>

# Toolkit Overview and Design Philosophy

## Purpose and Scope

`node804-mcp-toolkit` is a shared Python library that supplies the cross-cutting concerns every operations-oriented [Model Context Protocol](https://modelcontextprotocol.io) server needs: permission gating, audit logging, response shaping, TLS configuration, and a uniform set of tool-parameter conventions. The toolkit does not implement vendor-specific operations itself; instead, every MCP in the Node804 suite — currently [`node804-panos-mcp`](https://github.com/Node804) for Palo Alto firewalls and [`node804-freshservice-mcp`](https://github.com/Node804) for Freshservice ticketing, with PRTG and Veeam MCPs planned — depends on the toolkit so that those concerns are implemented once, identically, and securely. Source: [README.md:1-12]().

The library is fully typed (ships a `py.typed` marker and is `mypy --strict` clean) and targets Python 3.11+. It is published to PyPI as `node804-mcp-toolkit` and licensed under MIT. Source: [README.md:76-82]().

## Module Map

The toolkit is intentionally narrow. The public API is re-exported from `__init__.py` so callers typically write `from node804_mcp_toolkit import …` and never touch the submodules directly. Source: [src/node804_mcp_toolkit/__init__.py:1-39]().

| Module | Responsibility |
|---|---|
| `rbac` | Hierarchical `Mode` enum (`READ`, `STANDARD`, `FULL`, `ADMIN`) and a `ModeGate` decorator that registers tools with FastMCP only when the active mode qualifies |
| `audit` | JSON-lines `AuditEvent` model, `AuditSink` protocol, `audit` decorator, and `open_sink` factory wired to an env var |
| `lean` | Pure response-shaping helpers: `whitelist`, `strip_keys`, `filter_by_pattern`, `paginate` |
| `tls` | Verify-by-default `TlsConfig` resolved from `{PREFIX}_TLS_VERIFY` and `{PREFIX}_TLS_CA` env vars |
| `params` | Shared Pydantic parameter types: `VerboseFlag`, `FieldsList`, `Pagination`, `Pattern` |

Source: [README.md:14-23]().

### Mode-Based RBAC

`Mode` is an `IntEnum` ordered `READ < STANDARD < FULL < ADMIN`, so `if active_mode >= Mode.FULL` is the canonical way to express capability checks. Source: [src/node804_mcp_toolkit/rbac.py:75-83](). `ModeGate.from_env` reads the operator-supplied env var, parses it case-insensitively, and **falls back to `READ` on any missing or unrecognized value** — this is the "default deny on misconfiguration" stance. When the env var is set but invalid, a one-time stderr warning is emitted so the misconfiguration is visible in startup logs. Source: [src/node804_mcp_toolkit/rbac.py:33-58]().

The `gate.tool(mcp, required=Mode.…)` decorator records every decorated function in an internal registry. If `allows(required)` returns False, the function is **not** passed to `mcp.tool()`; it remains importable in Python but is invisible to the MCP client. Source: [src/node804_mcp_toolkit/rbac.py:120-141]().

### Audit Logging

Every tool call can emit one `AuditEvent` — a frozen Pydantic model with `ts`, `request_id`, `tool`, `category`, `mode`, sanitized `args`, `success`, `duration_ms`, and optional `extra`. Source: [src/node804_mcp_toolkit/audit.py:24-48](). The `audit` decorator wraps an async function, generates a UUID4 request id, measures wall-clock duration, redacts sensitive keys (e.g. `password`, `secret`, `token`, `authorization`, `x-api-key`) at any nesting depth, and elides any string value longer than 2048 characters. Source: [src/node804_mcp_toolkit/audit.py:62-86]().

`open_sink(env_var=…)` returns a `JsonlSink` when the env var is set, or a `_NoopSink` when it is not — so the default deployment pays nothing for audit. The sink catches and warns on write failures so a broken log file never breaks a tool call. Source: [src/node804_mcp_toolkit/audit.py:90-122]().

### Lean Responses

The `lean` helpers are deliberately pure: no I/O, no global state, easy to test and compose. The typical assembly order is `strip_keys` (drop SDK internals like `@uuid`) → `whitelist` (project the default field set) → `filter_by_pattern` (server-side substring filter) → `paginate` (enforce `limit`/`offset`). Source: [src/node804_mcp_toolkit/lean.py:1-16](). Whitelisting is intentionally shallow; tools that need nested projection compose multiple `whitelist` calls. Source: [src/node804_mcp_toolkit/lean.py:18-37]().

### Shared Parameter Conventions

`VerboseFlag`, `FieldsList`, `Pagination`, and `Pattern` are `Annotated` Pydantic types whose `description` strings are tuned for AI clients. `VerboseFlag` defaults to `False` with a description noting that the lean path typically saves 40–70% of tokens. Source: [src/node804_mcp_toolkit/params.py:16-25](). `Pagination` defaults to `limit=100`, `1 ≤ limit ≤ 1000`, with a hard cap that exists because "dumping unbounded inventories can blow the AI's context window." Source: [src/node804_mcp_toolkit/params.py:55-70]().

### TLS Resolution

`resolve_tls_config(env, prefix=…)` returns a `TlsConfig` whose `verify` field defaults to `True`. A `*_TLS_VERIFY=false` opt-out emits a stderr warning; `*_TLS_CA=/path.pem` loads a custom CA bundle for internal PKI. A CA file that fails to read triggers a soft warning and falls back to the system trust store — verification is still on, just against the default roots. Source: [src/node804_mcp_toolkit/tls.py:1-30]().

## Design Philosophy

Four principles, each enforced in code rather than documented as aspiration, shape the toolkit. Source: [README.md:60-65]().

- **One toolkit, one set of patterns.** The parameter names `verbose`, `fields`, `limit`, `offset`, and `pattern` mean the same thing in every MCP. A user who learns them once can drive any server in the suite without re-reading docs.
- **Default deny.** Unset or misconfigured mode env vars fall back to read-only. Tools above the active mode are never registered, so the AI client cannot even see them, let alone invoke them.
- **Token-lean by default.** Responses expose the fields the AI actually reasons over; `verbose=true` opts into the full payload, and `fields=[…]` projects an arbitrary subset.
- **Best-effort observability.** Audit logging never breaks a tool call. A sink failure logs once to stderr and moves on, so logging is a feature you can turn on without making the service fragile.

## Composition Pattern

The toolkit's value is in how the pieces compose. The README's "Quick start" shows the canonical pattern: `open_sink` and `ModeGate.from_env` are constructed once at startup, then `gate.tool(mcp, required=Mode.…)` decorates each tool. The gate wraps the function with `audit` automatically when a sink is attached, so most call sites never invoke the `audit` decorator directly. Source: [README.md:25-41](). A `summary()` method on the gate returns a stable shape (`mode`, `tools_total`, `tools_enabled`, `tools_blocked`, `enabled_tools`, `blocked_tools`, `audit`) that powers `server_status` tools across the suite. Source: [src/node804_mcp_toolkit/rbac.py:147-160]().

```mermaid
flowchart LR
    A[env: PREFIX_MODE] --> B[ModeGate.from_env]
    C[env: PREFIX_AUDIT_LOG] --> D[open_sink]
    D --> B
    B --> E[gate.tool decorator]
    E -->|mode qualifies| F[register with FastMCP + audit wrap]
    E -->|mode insufficient| G[record blocked, do not register]
    H[Tool call] --> F
    F --> I[sanitize args]
    I --> J[emit AuditEvent JSONL]
    F --> K[strip_keys → whitelist → filter → paginate]
    K --> L[Token-lean response]
```

## See Also

- [README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md) — installation, module table, and quick-start examples
- `src/node804_mcp_toolkit/rbac.py` — `Mode`, `ModeGate`, and registration semantics
- `src/node804_mcp_toolkit/audit.py` — `AuditEvent` schema, redaction rules, sink lifecycle
- `src/node804_mcp_toolkit/lean.py` — pure response-shaping helpers
- `src/node804_mcp_toolkit/params.py` — shared Pydantic parameter types
- `src/node804_mcp_toolkit/tls.py` — env-driven `TlsConfig` resolution

---

<a id='page-rbac'></a>

## Permission Gating with ModeGate

### Related Pages

Related topics: [Toolkit Overview and Design Philosophy](#page-overview), [Audit Logging and Argument Sanitization](#page-audit)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py)
- [src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py)
- [src/node804_mcp_toolkit/__init__.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/__init__.py)
- [README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md)
</details>

# Permission Gating with ModeGate

## Overview

`ModeGate` is the toolkit's role-based access control primitive for MCP servers. Its central idea is **registration-time gating**: rather than checking permissions inside every tool call, the gate decides at server startup whether each tool is *registered with the MCP server at all*. Tools that don't qualify are not exposed to the AI client — there is nothing to call, no error to surface, and no path to misuse. ([src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py))

The gate follows a **default-deny** posture: if the mode env var (e.g. `PANOS_MODE`) is unset or holds an unrecognized value, the gate resolves to the lowest mode (`READ`). Operators must explicitly opt in to write capabilities, and the failure mode is intentionally conservative. ([README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md))

## Mode Hierarchy and Resolution

`Mode` is an `IntEnum` with four tiers, ordered so that higher values include all capabilities of lower ones. Comparison is hierarchical out of the box: `if active_mode >= Mode.FULL:` correctly admits both `full` and `admin`. ([src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py))

| Mode | Value | Suggested audit category | Typical use |
|---|---|---|---|
| `READ` | 0 | `READ` | Read-only — safe default |
| `STANDARD` | 1 | `WRITE` | Routine writes |
| `FULL` | 2 | `WRITE` | Broad operational writes |
| `ADMIN` | 3 | `ADMIN` | Destructive/admin (e.g. `commit`) |

The class method `Mode.parse(raw, default=default)` accepts upper- or lower-case strings and falls back to `default` when the input is empty, `None`, or unrecognized. `ModeGate.from_env` builds on top of it. ([src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py))

```mermaid
flowchart TD
    A["Server starts"] --> B["Read env var e.g. PANOS_MODE"]
    B --> C{"Value valid Mode?"}
    C -- Yes --> D["active_mode = parsed"]
    C -- No / Missing --> E["Fallback to default e.g. READ"]
    D --> F{"Invalid raw was set?"}
    E --> F
    F -- Yes --> G["One-time stderr warning"]
    F -- No --> H["Proceed silently"]
    G --> I["ModeGate initialized"]
    H --> I
```

When the env var is present but invalid and `warn_on_invalid=True`, the gate prints a one-line stderr message: `[node804-mcp-toolkit] WARNING: invalid {env_var}='{raw}'. Falling back to '{default.name.lower()}'. Valid modes: ...`. ([src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py))

## ModeGate Lifecycle and the `gate.tool` Decorator

A `ModeGate` is constructed with the active `mode`, the source `env_var` (for diagnostics), and an optional `audit_sink`. Internally it maintains `_registry: dict[str, tuple[Mode, AuditCategory, bool]]` — `tool_name → (required_mode, category, registered)` — which is populated as decorators fire and later powers `summary()`. ([src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py))

The `gate.tool()` decorator is the operational entry point. It accepts:

- `mcp` — the FastMCP server instance,
- `required` — minimum mode level needed to expose the tool,
- `category` — optional audit-category override (defaults to a sensible value from `_MODE_TO_CATEGORY`; falls back to `"UNKNOWN"` when no map entry exists, deliberately loud),
- `tool_name` — optional override (default `func.__name__`),
- `extra_extractor` — forwarded to `audit()` to pull MCP-specific fields (e.g. `firewall`) into the event's `extra`,
- `**mcp_tool_kwargs` — forwarded to `mcp.tool()`.

The decorator's branching is the heart of the gate: denied tools are recorded with `registered=False` and returned unchanged — they are **not** wrapped, **not** registered with MCP, and **invisible** to the AI client. Allowed tools are wrapped with `audit()` (when an enabled sink is present) and then handed to `mcp.tool(name=name, **mcp_tool_kwargs)`. ([src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py))

```python
from mcp.server.fastmcp import FastMCP
from node804_mcp_toolkit import Mode, ModeGate, open_sink

mcp = FastMCP("panos-mcp")
sink = open_sink(env_var="PANOS_AUDIT_LOG")
gate = ModeGate.from_env(env_var="PANOS_MODE", audit_sink=sink)

@gate.tool(mcp, required=Mode.READ)
async def get_security_rules(...): ...

@gate.tool(mcp, required=Mode.ADMIN)
async def commit(...): ...  # not registered at all unless PANOS_MODE=admin
```

The same pattern applies to every MCP in the suite — `node804-panos-mcp`, `node804-freshservice-mcp`, and planned PRTG/Veeam servers — and the public surface is re-exported from the top-level package. ([README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md)), ([src/node804_mcp_toolkit/__init__.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/__init__.py))

## Audit Composition and Diagnostics

When the gate is built with an `audit_sink`, every allowed tool is automatically wrapped by `audit(...)` without any extra decorator on the user's side. The wrapper records an `AuditEvent` per call — ISO-8601 timestamp, UUID4 `request_id`, tool name, category, active mode, sanitized args, success/error, and `duration_ms` — and writes it to the sink (typically a JSONL file). ([src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py))

Sink failures are best-effort: `open_sink` returns a `_NoopSink` (`enabled=False`) and prints a single stderr warning if the log directory cannot be initialized, so audit problems never break a tool call. ([src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py))

For diagnostics, `ModeGate` exposes:

- `summary()` — a dict with `mode`, `env_var`, `tools_total`, `tools_enabled`, `tools_blocked`, sorted lists of enabled/blocked tool names, and `audit: enabled|disabled`. The shape is stable across MCPs in the suite for cross-server tooling.
- `describe()` — a one-line stderr-friendly string, e.g. `mode='admin' (12/14 tools enabled, 2 blocked)`.
- `all_known_tools` — a `functools.cached_property` returning every decorated tool name in registration order.

These hooks are typically consumed by `server_status` tools and startup logs so operators can see exactly what is exposed at boot. ([src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py))

## See Also

- `audit` module — JSON-lines audit logging and argument sanitization. ([src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py))
- `params` module — shared Pydantic schemas (`VerboseFlag`, `FieldsList`, `Pagination`, `Pattern`) so every tool uses the same names, defaults, and descriptions. ([src/node804_mcp_toolkit/__init__.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/__init__.py))
- `lean` module — response-shaping helpers (`whitelist`, `strip_keys`, `paginate`, `filter_by_pattern`) for token-lean responses. ([README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md))
- `tls` module — verify-by-default TLS resolution with custom CA bundle support. ([README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md))

---

<a id='page-audit'></a>

## Audit Logging and Argument Sanitization

### Related Pages

Related topics: [Toolkit Overview and Design Philosophy](#page-overview), [Permission Gating with ModeGate](#page-rbac)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py)
- [src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py)
- [src/node804_mcp_toolkit/__init__.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/__init__.py)
- [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py)
- [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py)
- [src/node804_mcp_toolkit/tls.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/tls.py)
- [README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md)
</details>

# Audit Logging and Argument Sanitization

## Purpose and Scope

The `audit` module gives every MCP tool call a single JSON-lines record of *who called what, when, with which arguments, and whether it succeeded*. It is part of the cross-cutting toolkit that lets a suite of MCP servers (Palo Alto, Freshservice, PRTG, Veeam) share one observability story instead of each implementing its own logger.

The module has two halves that work together:

1. **Audit logging** — `AuditEvent`, `AuditSink`, `JsonlSink`, `_NoopSink`, `open_sink`, and the `audit()` decorator.
2. **Argument sanitization** — `sanitize_args()` and `_sanitize_value()`, which redact sensitive keys and truncate long strings before they hit the log.

The stated design intent is "best-effort observability": audit logging never breaks a tool call, so sink failures warn once to stderr and degrade silently thereafter (`Source: src/node804_mcp_toolkit/audit.py:1-80`). It is re-exported through the package's public API alongside `ModeGate`, `whitelist`, and the parameter types so downstream MCPs can `import node804_mcp_toolkit` and get the whole stack (`Source: src/node804_mcp_toolkit/__init__.py:1-30`).

## The AuditEvent Data Model

Every tool call emits exactly one `AuditEvent`. The model is `frozen=True`, so event objects are immutable once created (`Source: src/node804_mcp_toolkit/audit.py:40-75`). The fields are intentionally stable so cross-MCP log tooling can rely on them:

| Field | Type | Purpose |
|---|---|---|
| `ts` | `str` | ISO-8601 timestamp, millisecond precision, UTC |
| `request_id` | `str` | UUID4 correlating one call across logs |
| `tool` | `str` | MCP tool name (e.g. `get_security_rules`) |
| `category` | `AuditCategory` | `READ` / `WRITE` / `ADMIN` / `UNKNOWN` |
| `mode` | `str` | Active permission mode at call time |
| `args` | `dict[str, Any]` | Sanitized tool arguments |
| `success` | `bool` | `True` for successful calls, `False` for handler errors |
| `duration_ms` | `int` | Wall-clock duration of the call |
| `error` | `str \| None` | Error message when `success=False` |
| `extra` | `dict[str, Any]` | Per-MCP optional fields (e.g. `{'firewall': 'hq-fw'}`) |

`category` is a `Literal["READ", "WRITE", "ADMIN", "UNKNOWN"]`. The toolkit ships a suggested `Mode → AuditCategory` mapping (`READ → READ`, `STANDARD → WRITE`, `FULL → WRITE`, `ADMIN → ADMIN`) so the gate can pick a sensible category when the caller doesn't override it (`Source: src/node804_mcp_toolkit/rbac.py:55-75`). `UNKNOWN` is the default for tools that don't pass `category` to `gate.tool()` — deliberate so missing mappings show up in code review rather than being silently misclassified.

## Sinks and Configuration

`AuditSink` is a `Protocol` with one method, `write(event)`, plus an `enabled` flag. Two concrete implementations ship in the module:

- **`_NoopSink`** — drops events. `enabled=False`. Returned when the env var is unset so the audit decorator costs effectively nothing in the default case (`Source: src/node804_mcp_toolkit/audit.py:82-95`).
- **`JsonlSink`** — appends one JSON object per line to a file path. The parent directory is created on first use. Write failures catch `OSError`, emit a one-time stderr warning, and never raise — "audit must not break tool calls" (`Source: src/node804_mcp_toolkit/audit.py:98-130`).

`open_sink(env=None, *, env_var="MCP_AUDIT_LOG")` is the constructor used by every MCP in the suite. It returns a `JsonlSink` when the env var is set, a `_NoopSink` otherwise. Directory-creation failures fall back to no-op with a stderr warning (`Source: src/node804_mcp_toolkit/audit.py:132-160`).

```mermaid
flowchart LR
    A[Tool call] --> B[audit decorator]
    B --> C{Sink enabled?}
    C -- no --> D[_NoopSink: drop]
    C -- yes --> E[JsonlSink]
    E --> F[sanitize_args]
    F --> G[Append JSONL line]
    G --> H{Write OK?}
    H -- yes --> I[Done]
    H -- no --> J[Warn once to stderr]
    J --> I
```

The decorator is wired into MCP servers through `ModeGate.tool()`. When the gate accepts a tool and an audit sink is configured, it composes `audit(...)` on top of the user's function before handing it to `mcp.tool()` — so a server author writes one decorator call and gets both gating and logging (`Source: src/node804_mcp_toolkit/rbac.py:130-180`). `ModeGate.summary()` then reports `"audit": "enabled"` or `"audit": "disabled"` for `server_status` tools and startup logs (`Source: src/node804_mcp_toolkit/rbac.py:200-230`).

## Argument Sanitization

`sanitize_args()` is called by the `audit` decorator before emitting the event. It walks the input mapping recursively and:

1. **Redacts sensitive keys** at any nesting depth. The default set is `DEFAULT_SENSITIVE_KEYS = {api_key, apikey, password, passwd, secret, token, auth, authorization, x-api-key}`. Match is case-insensitive. The redaction string is the literal `"<redacted>"` (`Source: src/node804_mcp_toolkit/audit.py:160-200`).
2. **Elides long strings** over `DEFAULT_MAX_VALUE_LEN = 2048` chars, replaced with `"<elided: N chars>"`. This stops a single `set_config` element from blowing one audit line to 100K+ chars.
3. **Defensively stringifies** non-mapping, non-list values (`bytes`, `set`, custom classes) instead of crashing, so audit never raises inside the wrapping context.

The same defaults are documented as defense in depth — current tool schemas don't accept those keys (credentials live in the keychain, not in tool params), but a future tool that accidentally does will not leak them to the log (`Source: src/node804_mcp_toolkit/audit.py:165-175`). If `args` isn't a mapping at all (typical when a tool was called positionally with no kwargs), `sanitize_args` returns `{}` — the audit line stays typed and well-formed rather than carrying an oddly-shaped field (`Source: src/node804_mcp_toolkit/audit.py:182-195`).

## Integration With the Rest of the Toolkit

Sanitization is the last step before serialization, but it shares the same defaults philosophy as the rest of the package:

- **Shared parameter types** — tools that accept a `Pattern` or `Pagination` (both `Annotated` Pydantic types in `params.py`) feed into `filter_by_pattern` and `paginate` from `lean.py`, and the same `args` dict is what audit serializes (`Source: src/node804_mcp_toolkit/params.py:1-60`, `Source: src/node804_mcp_toolkit/lean.py:1-30`).
- **Single TLS posture** — `tls.py` resolves a `TlsConfig` from env vars with the same "verify-by-default, opt-out via `{PREFIX}_TLS_VERIFY=false`" pattern that `open_sink` uses for `MCP_AUDIT_LOG`. The two are independent — TLS controls outbound traffic, audit controls outbound observability — but they share the convention of "explicit env var to enable, silent default to safe" (`Source: src/node804_mcp_toolkit/tls.py:1-40`).
- **Default-deny posture** — `ModeGate.from_env()` falls back to `Mode.READ` on missing or invalid env values, and the README names this as a deliberate design choice: "Default deny. Unset or misconfigured mode env vars fall back to read-only" (`Source: README.md:55-80`). Audit logs capture the mode that was *actually* active at call time, not the requested one, so post-hoc reviews see the real permission context.

The result is a stack where one decorator on each tool gives you a streaming JSONL audit trail, redacted credentials, bounded line length, and no failure mode that can take down a tool call — exactly the behavior promised in the package overview (`Source: README.md:15-40`).

## See Also

- [Mode-Gated Tools and Permission Tiers](#)
- [Token-Efficient Response Shaping](#)
- [TLS Configuration for Outbound MCP Clients](#)

---

<a id='page-lean-params'></a>

## Response Shaping and Shared Parameter Schemas

### Related Pages

Related topics: [Toolkit Overview and Design Philosophy](#page-overview)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py)
- [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py)
- [src/node804_mcp_toolkit/__init__.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/__init__.py)
- [README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md)
- [src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py)
- [src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py)
</details>

# Response Shaping and Shared Parameter Schemas

## Overview

`node804-mcp-toolkit` ships two complementary modules that work together to keep MCP responses small and the parameter surface consistent across every server in the suite. The `lean` module provides pure response-shaping helpers (`whitelist`, `strip_keys`, `paginate`, `filter_by_pattern`), while the `params` module exposes a uniform set of Pydantic-annotated types (`VerboseFlag`, `FieldsList`, `Pagination`, `Pattern`) that every tool uses for its arguments. Together they form the "token-lean by default" half of the toolkit's design philosophy: tools project only the fields the AI actually reasons over, and the conventions for asking for "more" or "less" are identical regardless of which MCP a caller is using. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py), [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py), [README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md).

The public API re-exports both groups from the package root, so downstream MCPs import them with a single statement. Source: [src/node804_mcp_toolkit/__init__.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/__init__.py).

## Response-Shaping Utilities (`lean.py`)

The `lean` module is intentionally I/O-free so the helpers compose in any order and are trivial to unit-test. The docstring in `lean.py` documents a canonical tool-call recipe: `strip_keys` to drop SDK noise, `whitelist` to keep only the fields the tool should expose, `filter_by_pattern` to apply the caller's name filter, then `paginate` to enforce `limit`/`offset` before serialization. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).

### `whitelist`

`whitelist` accepts either a single mapping or an iterable of mappings and returns the same shape with non-listed top-level keys removed. Field names that are absent in the input are silently skipped, so callers can pass a generous whitelist. Whitelisting is intentionally shallow — nested dicts pass through unchanged, and tools that need nested filtering should compose multiple calls. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).

### `strip_keys`

`strip_keys` removes a configurable set of keys from a mapping, with an optional `recursive` flag that walks nested dicts and lists. Typical use is dropping SDK internals such as UUIDs, dirty-state flags, or `@loc` annotations that the AI never needs. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).

### `paginate`

`paginate` slices a list with `items[offset : offset + limit]`. It raises `ValueError` for negative `limit` or `offset`, and returns an empty list (rather than raising) when `offset` exceeds the list length — pagination is meant to be forgiving. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).

### `filter_by_pattern`

`filter_by_pattern` filters items by substring or regex on a configurable `name_field` (default `"name"`). The default is case-insensitive substring matching, which matches typical AI intent ("find rules matching 'social'"); passing `use_regex=True` switches to regex compilation with the same case flag. Items missing `name_field` are excluded — the docstring explains this is "no implicit pass-through" because a missing name field usually signals the caller used the wrong filter on the wrong list. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).

## Shared Parameter Schemas (`params.py`)

The `params` module is the consistency layer for tool signatures. Every tool that accepts a verbose flag, a field whitelist, pagination, or a pattern filter uses the same names, defaults, and descriptions, so a user who learns `verbose` / `fields` / `limit` / `offset` / `pattern` in one MCP finds the same conventions in every other MCP. Source: [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py).

| Schema | Type | Default | Purpose |
|---|---|---|---|
| `VerboseFlag` | `bool` | `False` | Opt into the full underlying response; the tool's default returns only the most relevant fields (typical 40–70% token reduction). |
| `FieldsList` | `list[str] \| None` | `None` | Explicit per-call field projection; overrides the default whitelist and takes precedence over `verbose`. |
| `Pattern` | `str \| None` | `None` | Server-side case-insensitive substring filter on the item's name field; avoids the "dump everything and filter client-side" pattern. |
| `Pagination` | `BaseModel` | `limit=100, offset=0` | `limit` clamped to `[1, 1000]`; hard cap exists because unbounded inventories can blow the AI's context window. |

Source: [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py).

The `Pagination` model is a `BaseModel` with `limit` constrained to `ge=1, le=1000`; the 100-item default is tuned for token efficiency, and callers page through larger inventories by incrementing `offset` between calls. Source: [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py).

## Composition and End-to-End Flow

The intended pipeline — visible both in the module docstring and in the README's usage example — is: drop SDK internals, optionally project fields, apply a name filter, then paginate. The README demonstrates the exact ordering for `get_security_rules`: `strip_keys` → `filter_by_pattern` → `paginate` → `whitelist`. Source: [README.md](https://github.com/Node804/node804-mcp-toolkit/blob/main/README.md).

```mermaid
flowchart LR
    A[Raw SDK response] --> B[strip_keys]
    B --> C[filter_by_pattern]
    C --> D[paginate]
    D --> E[whitelist]
    E --> F[Lean MCP response]
```

The schemas in `params.py` are the inputs the AI client uses to request each step. `Pattern` controls `filter_by_pattern`, `Pagination.limit`/`offset` control `paginate`, `FieldsList` controls the final `whitelist`, and `VerboseFlag` short-circuits the pipeline to return the full payload when the caller needs uncommon fields such as UUIDs or audit timestamps. Source: [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py), [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).

In practice, the gating decorator from `rbac.py` and the audit decorator from `audit.py` wrap the tool function itself, while `lean` and `params` operate on the function's return value. This separation keeps RBAC, audit, response shaping, and parameter typing independently testable. Source: [src/node804_mcp_toolkit/rbac.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/rbac.py), [src/node804_mcp_toolkit/audit.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/audit.py).

## Common Pitfalls

- **Whitelisting is shallow.** Nested filtering requires multiple `whitelist` calls; do not assume `whitelist` recurses. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).
- **`filter_by_pattern` drops items missing `name_field`.** If the input list does not use `"name"`, pass `name_field` explicitly. Source: [src/node804_mcp_toolkit/lean.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/lean.py).
- **`FieldsList` overrides `verbose`.** A caller that sets `fields=[...]` gets exactly those keys even if `verbose=True`. Source: [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py).
- **`Pagination.limit` is hard-capped at 1000.** Tool authors who need a larger page must split it across calls. Source: [src/node804_mcp_toolkit/params.py](https://github.com/Node804/node804-mcp-toolkit/blob/main/src/node804_mcp_toolkit/params.py).

## See Also

- RBAC and Mode-Based Tool Registration
- Audit Logging
- TLS Configuration for Outbound Clients

---

<!-- evidence_pipeline_checked: true -->

---

## Pitfall Log

Project: Node804/node804-mcp-toolkit

Summary: Found 6 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Capability evidence risk - Capability evidence risk requires verification.

## 1. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/Node804/node804-mcp-toolkit

## 2. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/Node804/node804-mcp-toolkit

## 3. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/Node804/node804-mcp-toolkit

## 4. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/Node804/node804-mcp-toolkit

## 5. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/Node804/node804-mcp-toolkit

## 6. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/Node804/node804-mcp-toolkit

<!-- canonical_name: Node804/node804-mcp-toolkit; human_manual_source: deepwiki_human_wiki -->
