Skip to content

feat(sql_sanitizer): add Rust-backed SQL sanitizer plugin - #133

Merged
lucarlig merged 13 commits into
mainfrom
feat/sql-sanitizer-plugin
Jul 15, 2026
Merged

feat(sql_sanitizer): add Rust-backed SQL sanitizer plugin#133
lucarlig merged 13 commits into
mainfrom
feat/sql-sanitizer-plugin

Conversation

@madhu-mohan-jaishankar

@madhu-mohan-jaishankar madhu-mohan-jaishankar commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

🔗 Related Issue

Closes #130


📝 Summary

Adds cpex-sql-sanitizer, a Rust-backed SQL sanitizer plugin for the MCP Gateway.

The plugin intercepts prompt_pre_fetch and tool_pre_invoke hooks and scans SQL argument values for security issues before they reach the backend. All detection logic is implemented in Rust via PyO3 and exposed to the gateway through a thin Python shim (SQLSanitizerPlugin).

Key capabilities:

  • Per-statement analysis — the SQL payload is split on ; and each statement is checked independently. This closes a correctness gap where a WHERE clause in one statement could mask a WHERE-less DELETE/UPDATE in another statement in the same payload.
  • Blocked statement patternsDROP, TRUNCATE, ALTER, GRANT, REVOKE blocked by default via configurable regex patterns.
  • Unsafe DML detectionDELETE FROM and UPDATE without a WHERE clause are flagged.
  • Comment stripping-- line comments and /* */ block comments are stripped before analysis so patterns hidden in comments are not matched.
  • Field filtering — when fields is set, only the named argument keys are scanned; all string values are scanned when fields is null.
  • Monitoring mode — when block_on_violation=false, violations are recorded in result.metadata.sql_issues and the request is passed through, enabling audit-only deployments.
  • Interpolation heuristic — optional detection of +, %., and {…} patterns that indicate non-parameterized SQL construction.

📏 Reviewability

  • This PR has one clear purpose
  • The linked issue is not labeled triage
  • Unrelated bugs or improvements are tracked in separate issues/PRs
  • Tests are included with the code they validate
  • If AI-assisted, I understand and can explain the generated changes

🏷️ Type of Change

  • Bug fix
  • Feature / Enhancement
  • Documentation
  • Refactor
  • Chore (deps, CI, tooling)
  • Other (describe below)

🧪 Verification

Check Command Status
Formatting cargo fmt --check ✅ clean
Lint cargo clippy -- -D warnings ✅ 0 warnings
Unit tests (Rust) cargo test --lib ✅ 18/18 passed
Integration tests (Python) make test-integration ⬜ requires make install (maturin wheel build)

Unit test run output:

running 18 tests
test comments::tests::strips_block_comments ... ok
test comments::tests::strips_line_comments ... ok
test comments::tests::strips_multiline_block_comment ... ok
test comments::tests::no_comments_unchanged ... ok
test issues::tests::blocks_drop_table ... ok
test issues::tests::blocks_truncate ... ok
test issues::tests::detects_delete_without_where ... ok
test issues::tests::no_issue_for_delete_with_where ... ok
test issues::tests::detects_update_without_where ... ok
test issues::tests::no_issue_for_update_with_where ... ok
test issues::tests::per_statement_fix_where_in_later_statement_does_not_hide_earlier_violation ... ok
test issues::tests::no_issue_for_single_update_with_where ... ok
test issues::tests::comments_hide_drop_before_strip_is_applied ... ok
test issues::tests::detects_interpolation_when_required ... ok
test issues::tests::no_false_positive_interpolation_when_not_required ... ok
test plugin::tests::allows_safe_select ... ok
test plugin::tests::blocks_unsafe_delete ... ok
test plugin::tests::per_statement_fix_four_updates_all_blocked ... ok
test result: ok. 18 passed; 0 failed; 0 ignored

✅ Checklist

  • Code formatted (cargo fmt)
  • Tests added/updated for changes
  • Documentation updated (if applicable)
  • No secrets or credentials committed

📓 Notes

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
@lucarlig

Copy link
Copy Markdown
Collaborator

@madhu-mohan-jaishankar make sure you have a https://pypi.org/ package setup to upload to should be cpex-sql-sanitizer, and set as mantainer you and @jonpspri

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new standalone plugin package, cpex-sql-sanitizer, to the CPEX plugins monorepo. The plugin is implemented as a Rust core (PyO3) with a thin Python Plugin shim, intended to detect risky SQL patterns in prompt_pre_fetch and tool_pre_invoke hook arguments and optionally block or report them.

Changes:

  • Introduces Rust implementation for SQL scanning (per-statement checks, comment stripping, blocked patterns, unsafe DML detection, interpolation heuristic).
  • Adds Python shim + plugin manifest + packaging/build tooling (maturin, Makefile, entry point).
  • Adds integration tests for gateway-facing behavior (blocking, monitoring mode metadata, field filtering, modified payload on stripping).

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
plugins/tests/sql_sanitizer/test_integration.py Adds Python integration tests asserting expected hook behavior and result shapes.
plugins/rust/python-package/sql_sanitizer/src/scanner.rs Implements recursive arg scanning, field filtering, and stripped-value accumulation.
plugins/rust/python-package/sql_sanitizer/src/plugin.rs Implements Rust-owned hook handling and builds framework result/violation objects.
plugins/rust/python-package/sql_sanitizer/src/lib.rs Defines the PyO3 module and registers the core class.
plugins/rust/python-package/sql_sanitizer/src/issues.rs Implements SQL issue detection (blocked patterns, DML-without-WHERE, interpolation heuristic).
plugins/rust/python-package/sql_sanitizer/src/config.rs Parses Python config into compiled Rust configuration (incl. regex compilation).
plugins/rust/python-package/sql_sanitizer/src/comments.rs Implements SQL comment stripping utilities and unit tests.
plugins/rust/python-package/sql_sanitizer/src/bin/stub_gen.rs Adds optional stub generation binary for Python typing stubs.
plugins/rust/python-package/sql_sanitizer/pyproject.toml Declares Python package metadata, deps, and maturin module configuration.
plugins/rust/python-package/sql_sanitizer/Makefile Adds standard plugin dev/test/build targets for this package.
plugins/rust/python-package/sql_sanitizer/cpex_sql_sanitizer/sql_sanitizer.py Adds the Python Plugin shim delegating to the Rust core.
plugins/rust/python-package/sql_sanitizer/cpex_sql_sanitizer/plugin-manifest.yaml Declares plugin metadata, hooks, and default config.
plugins/rust/python-package/sql_sanitizer/cpex_sql_sanitizer/init.py Adds lazy exports for the shim and Rust core class.
plugins/rust/python-package/sql_sanitizer/Cargo.toml Defines the Rust crate, dependencies, features, and benches.
plugins/rust/python-package/sql_sanitizer/benches/sql_sanitizer.rs Adds Criterion benchmarks for hot paths.
Cargo.toml Registers sql_sanitizer as a workspace member.
Cargo.lock Adds the new crate to the lockfile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/plugin.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs Outdated
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 14, 2026 16:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 6 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/plugin.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/plugin.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/README.md Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/cpex_sql_sanitizer/sql_sanitizer.py Outdated
…mutation tests

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 14, 2026 17:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 6 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/plugin.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/README.md Outdated
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 15, 2026 09:48
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 5 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/plugin.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/README.md
Copilot AI review requested due to automatic review settings July 15, 2026 09:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 7 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/README.md Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/plugin.rs
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 15, 2026 10:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 5 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/README.md
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/comments.rs Outdated

@lucarlig lucarlig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes based on packaged-wheel, framework-isolation, SQLite, and build testing:

  1. CPEX isolation fails open. scan_args uses PyDict::iter, but CopyOnWriteDict keeps visible values outside the C-level dict. A packaged DROP TABLE users call returned continue_processing=True. Use the mapping protocol and add a wrap_payload_for_isolation regression test.

  2. Valid destructive SQL bypasses detection. Quoted-target updates, UPDATE ... SET note='WHERE', and DELETE ... RETURNING 'WHERE' were allowed and changed/deleted every SQLite row. The scanner must distinguish identifiers, literals, and actual clauses. It also blocks safe semicolons/keywords inside literals.

  3. Comment stripping corrupts payloads. Comment markers inside quoted literals are removed, while nested replacements overwrite/inject top-level keys and nested lists are skipped. Stripping and rebuilding need to be syntax-aware and path-preserving.

  4. Advertised Rust targets fail. make bench-no-run imports the wrong crate name, and all-feature stub_gen compilation fails against the workspace's pyo3-stub-gen 0.23 API.

  5. Malformed config fails open. blocked_statements=[123] silently becomes an empty blocked-pattern set and allows DROP TABLE.

Please address these before merge.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 15, 2026 12:12
@madhu-mohan-jaishankar

Copy link
Copy Markdown
Collaborator Author

Requesting changes based on packaged-wheel, framework-isolation, SQLite, and build testing:

  1. CPEX isolation fails open. scan_args uses PyDict::iter, but CopyOnWriteDict keeps visible values outside the C-level dict. A packaged DROP TABLE users call returned continue_processing=True. Use the mapping protocol and add a wrap_payload_for_isolation regression test.
  2. Valid destructive SQL bypasses detection. Quoted-target updates, UPDATE ... SET note='WHERE', and DELETE ... RETURNING 'WHERE' were allowed and changed/deleted every SQLite row. The scanner must distinguish identifiers, literals, and actual clauses. It also blocks safe semicolons/keywords inside literals.
  3. Comment stripping corrupts payloads. Comment markers inside quoted literals are removed, while nested replacements overwrite/inject top-level keys and nested lists are skipped. Stripping and rebuilding need to be syntax-aware and path-preserving.
  4. Advertised Rust targets fail. make bench-no-run imports the wrong crate name, and all-feature stub_gen compilation fails against the workspace's pyo3-stub-gen 0.23 API.
  5. Malformed config fails open. blocked_statements=[123] silently becomes an empty blocked-pattern set and allows DROP TABLE.

Please address these before merge.

Thanks for the thorough review, @lucarlig! All points addressed in the latest commit (9ccdaad):

Issue 1 — CopyOnWriteDict / CPEX isolation:
Switched all three PyDict::iter() / cast::<PyDict>() call sites (scan_value, scan_args, rebuild_args_with_stripped) to Python-level call_method0("items") + try_iter(), which respects any __iter__/items() override. Added wrap_payload_for_isolation regression test that constructs a CopyOnWriteDict with an empty C-level hash table and verifies the DELETE FROM users payload is still blocked.

Issue 2 — WHERE/keywords inside string literals:
Added mask_string_literals() (replaces literal content with '') and split_statements() (;-split respecting literals). find_issues_in_statement now runs all checks on the masked SQL. Added 3 regression tests: where_in_string_literal_is_not_treated_as_clause, semicolon_in_string_literal_does_not_split_statement, interpolation_marker_in_string_literal_is_not_flagged.

Issue 3a — Comment stripping corrupts literals:
Replaced the two-regex approach in comments.rs with a character-level state machine that tracks in_quote (including '' escaped-quote handling), so '-- not a comment' and '/* stays */' are preserved. Added 4 new tests.

Issue 3b — List-item stripping limitation:
Added a // NOTE: comment in the list-string branch of scan_value explaining why those items are scanned but excluded from stripped.

Issue 4 — Bench crate name:
Fixed use sql_sanitizer::use sql_sanitizer_rust:: and switched criterion::black_boxstd::hint::black_box.

Issue 5 — Malformed config fails open:
Changed the else { continue; } branch in blocked_statements parsing to return Err(PyTypeError) with a descriptive message, so invalid item types are surfaced immediately rather than silently producing an empty pattern set.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

plugins/rust/python-package/sql_sanitizer/Makefile:128

  • The ci target currently runs install (editable maturin develop) and skips install-wheel/stub verification. The CI workflow runs make ci on non-Linux runners, and other plugin Makefiles avoid calling install inside ci to ensure wheel-based verification and consistent behavior across OSes. Consider aligning ci/ci-build with the standard pattern (ci-build: check-all verify-stubs build … install-wheel, ci: ci-build test-integration).
ci-build: fmt-check clippy test-unit build

ci: fmt-check clippy test-unit install test-integration

Comment thread plugins/rust/python-package/sql_sanitizer/src/bin/stub_gen.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/bin/stub_gen.rs
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 15, 2026 12:39

@lucarlig lucarlig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for four remaining issues:

  1. Quoted-table UPDATE bypass
    src/issues.rs:24 only matches a \w+ target. UPDATE "users" SET admin=1 is allowed; executing it in SQLite changed every row.

  2. Nested comment stripping overwrites the wrong field
    src/scanner.rs records only (key, value), then src/plugin.rs applies it at the top level. Stripping nested wrapper.sql overwrites an unrelated top-level sql while leaving the nested value unchanged.

  3. Nested lists bypass scanning
    src/scanner.rs does not recurse into lists within lists. {"batch": [["DROP TABLE users"]]} is allowed without a violation.

  4. Stub-generator build is broken
    cargo check -p sql_sanitizer --all-features --lib --bins fails because src/bin/stub_gen.rs uses the old pyo3-stub-gen API (GenerateOptions and generate(&options)).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 5 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs Outdated
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs
Comment thread plugins/rust/python-package/sql_sanitizer/README.md
Comment thread plugins/rust/python-package/sql_sanitizer/src/config.rs
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 15, 2026 13:11
@madhu-mohan-jaishankar

Copy link
Copy Markdown
Collaborator Author

Requesting changes for four remaining issues:

  1. Quoted-table UPDATE bypass
    src/issues.rs:24 only matches a \w+ target. UPDATE "users" SET admin=1 is allowed; executing it in SQLite changed every row.
  2. Nested comment stripping overwrites the wrong field
    src/scanner.rs records only (key, value), then src/plugin.rs applies it at the top level. Stripping nested wrapper.sql overwrites an unrelated top-level sql while leaving the nested value unchanged.
  3. Nested lists bypass scanning
    src/scanner.rs does not recurse into lists within lists. {"batch": [["DROP TABLE users"]]} is allowed without a violation.
  4. Stub-generator build is broken
    cargo check -p sql_sanitizer --all-features --lib --bins fails because src/bin/stub_gen.rs uses the old pyo3-stub-gen API (GenerateOptions and generate(&options)).
  1. UPDATE_RE bypass with quoted identifiers — extended the table-name pattern to match double-quoted (ANSI), backtick (MySQL), and bracket (SQL Server) identifiers, so UPDATE "users" SET admin=1 is no longer skipped by the WHERE-clause guard. Added a regression test quoted_table_name_update_is_blocked.

  2. Nested comment-stripping corrupting top-level fieldsscan_value now takes an at_top_level flag and only records stripped values at depth 0. Nested strings are still scanned for issues, but their stripped forms aren't emitted, since rebuild_args_with_stripped applies a shallow top-level overlay and would otherwise overwrite an unrelated key of the same name. Documented this as a known limitation in the module comment. Test: nested_comment_stripping_does_not_overwrite_top_level_key.

  3. Nested lists not scanned — added list-within-list recursion, so payloads like {"batch": [["DROP TABLE users"]]} are fully walked. Test: nested_list_items_are_scanned.

  4. stub_gen API — switched to the stub_info().generate() API (now on pyo3-stub-gen 0.23.0 after merging main) and removed the dead GENERATED_ALL_MARKER constant.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 5 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/config.rs
Comment thread plugins/rust/python-package/sql_sanitizer/README.md
Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 15, 2026 13:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 4 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/config.rs
Comment thread plugins/rust/python-package/sql_sanitizer/README.md
Comment thread plugins/rust/python-package/sql_sanitizer/src/issues.rs

@lucarlig lucarlig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two merge blockers remain:

  1. MySQL destructive DELETE bypass. Both DELETE u FROM users AS u and DELETE FROM users # WHERE id=1 return continue_processing=True. The first is valid multi-table DELETE syntax; the second hides the apparent WHERE inside a MySQL # comment. Both can delete every row.

  2. Release preflight will fail. No .pyi files are tracked for this plugin, while release-rust-python-package.yaml requires at least one in a fresh checkout. Please generate and commit the package stubs.

Signed-off-by: Madhu Mohan Jaishankar <madhu.mohan.jaishankar@ibm.com>
Copilot AI review requested due to automatic review settings July 15, 2026 15:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 24 changed files in this pull request and generated 4 comments.

Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs
Comment thread plugins/rust/python-package/sql_sanitizer/src/scanner.rs
Comment thread plugins/rust/python-package/sql_sanitizer/README.md

@lucarlig lucarlig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lucarlig
lucarlig merged commit 4fee8ce into main Jul 15, 2026
33 checks passed
@lucarlig
lucarlig deleted the feat/sql-sanitizer-plugin branch July 15, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate SQL Sanitizer plugin from mcp-context-forge

3 participants