Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .secrets.baseline

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ clap = { version = "4.5.60", features = ["derive", "env"] }
thiserror = "2.0.18"
rmp-serde = "1.3.1"
async-trait = "0.1.89"
base64 = "0.22.1"
reqwest = "0.13"
jsonwebtoken = { version = "11.0.0", features = ["aws_lc_rs"] }
rustls = { version = "0.23", features = ["ring"] }
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ Activation requires all three pieces:
- Runtime flag: `--runtime-plugins-enabled true`
- Redis config key: `ContextForgeGatewayRuntimePluginConfig`

The plugin kind is `validator/secrets-detection`. The data plane currently
wires only `cmf.tool_pre_invoke` and `cmf.tool_post_invoke`.
The plugin kind is `validator/secrets-detection`. The dataplane wires CMF hooks
for tool calls, prompt fetches, and resource reads.

Example run command:

Expand Down
8 changes: 5 additions & 3 deletions _context/wiki/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,11 @@ The binary sets `tikv_jemallocator` as the global allocator. jemalloc holds up b
- `initialize` opens one backend transport per configured backend concurrently (`futures::future::join_all`); a failed backend degrades that backend only.
- List methods fan out to all connected backends concurrently and merge.
- Targeted calls (except `call_tool`) resolve exactly one backend service handle from `BackendTransports`.
- `call_tool` creates a fresh per-request backend connection via `connect_backend_for_request`, runs pre/post plugin hooks, executes the call, then explicitly closes the connection before returning.
- Targeted tool, prompt, and resource calls run configured pre/post plugin hooks after backend routing. `call_tool` creates a fresh per-request backend connection via `connect_backend_for_request`, then explicitly closes it before returning.
- `call_tool` watches the downstream cancellation token and forwards a cancel to the backend if the client gives up first; backend progress notifications are forwarded downstream while the call is in flight.

Resource reads carry a concrete, request-owned hook state across backend I/O. It pins the runtime selected before the read, or records that no post hook was configured. Post processing consumes that state without type erasure, downcasts, or a second registry lookup.

## Startup And Response Flow

Startup sequence (`main.rs` → `Gateway::run_gateway`):
Expand All @@ -185,7 +187,7 @@ Response unwind order (Tower layers execute outside-in, so unwind is inside-out)

```text
backend response
-> response plugin hooks (call_tool only)
-> response plugin hooks (tool, prompt, and resource calls)
-> merge / namespace / pass through
-> virtual_host_config_layer response side
-> user_config_store_layer response side
Expand Down Expand Up @@ -232,7 +234,7 @@ Do not bury transport security decisions inside MCP method handlers. They belong

## Plugin Hook Expansion Requirements

Current supported hooks are intentionally narrow (`cmf.tool_pre_invoke`, `cmf.tool_post_invoke`). Before adding any new hook point, define all of the following:
Current supported hooks cover tool, prompt, and resource pre/post lifecycles. Before adding any new hook point, define all of the following:

| Requirement | Why |
| --- | --- |
Expand Down
14 changes: 11 additions & 3 deletions _context/wiki/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ RuntimePluginConfigDocument
cpex: CpexConfig
```

Supported: `cmf.tool_pre_invoke`, `cmf.tool_post_invoke`, `cmf.prompt_pre_fetch`, `cmf.prompt_post_fetch` only.
Rejected: routing-based selection, plugin dirs, global policies, resource and LLM hooks, plugin conditions.
Supported: tool, prompt, and resource pre/post CMF hooks.
Rejected: routing-based selection, plugin dirs, global policies, LLM hooks, plugin conditions.
Config validation and `CmfPluginFactory` registration must agree on that list: a hook accepted by validation but not registered leaves the plugin loaded and silently inert.
Reload watcher: 10-minute interval. Invalid reload → runtime marked failed.

Expand All @@ -212,7 +212,15 @@ Writing plugin edits back follows three rules:

MCP prompt results carry no error flag, so a plugin setting `is_error` on the CMF prompt result is rejecting the prompt rather than describing it. The gateway turns that into an MCP error carrying the plugin's `error_message`, and the rendered content never reaches the client. This differs from tools, where `is_error` is a field on `CallToolResult` and is forwarded as a successful response.

Binary resource blobs reach plugins by URI and MIME type but not by content: CMF stores decoded bytes while MCP sends base64. A plugin can deny such a message; editing one fails the write-back.
Binary resources embedded in prompts reach plugins by URI and MIME type but not by content. A plugin can deny such a message; editing one fails the write-back. Resource-read hooks below have their own binary conversion.

### Resource Read Hook Behavior

For `resources/read`, the pre hook receives the canonical backend-local URI and may allow, deny or rewrite it. A rewritten URI must resolve unambiguously through the caller's published virtual-host resources before a backend connection is opened. Aliases for the same backend target do not create ambiguity.

The post hook may replace each returned resource's text or binary content, URI and MIME type, including converting text to a blob or a blob to text. Existing MCP `_meta` is preserved. CMF-only envelope and descriptive fields do not restrict these changes. Each resource still needs a valid MCP content representation; binary resource reads are decoded for CPEX and re-encoded after edits, while unchanged blob bytes retain their original wire value. This resource path does not add prompt-wide payload validation.

The pre call returns an opaque, concrete `ResourceHookState` consumed by the post call. It captures both the runtime and the decision to run or skip post hooks before backend I/O. A reload only affects subsequent requests, including when it enables or disables resource hooks. Callers cannot construct missing or mismatched active state, and requests without a post hook allocate no correlation state.

### Demo Plugin Workflow

Expand Down
4 changes: 2 additions & 2 deletions _context/wiki/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ backends without recompiling a shared support tree for every feature file.
| Area | Covers |
| --- | --- |
| `gateway/{tools,prompts,resources,subscriptions}.rs` | Active routed operations and exact routing failures. |
| `gateway/plugins.rs` | Gateway-owned CPEX ordering, mutation, denial, progress, and prompt seams using deterministic recording plugins. Concrete plugin behavior stays in each plugin crate. |
| `gateway/plugins.rs` | Gateway-owned CPEX ordering, mutation, denial, progress, and prompt seams using deterministic recording plugins. Resource coverage includes direct and aliased URIs, text/blob conversion, canonical pre-hook URIs, published-target rewrites, rejection of unpublished targets, metadata preservation, and pre/post denial. Concrete plugin behavior stays in each plugin crate. |
| `gateway/harness/` | Authentication, modern and compatibility clients, in-memory configuration, concrete mock backends, and owned server fixtures. |
| `gateway/future_contracts/` | Deferred fanout, pagination, TLS, completions, subscriptions, and cancellation contracts. |

Expand All @@ -56,7 +56,7 @@ cargo nextest list --locked --workspace --all-features --run-ignored only
```

The two binary E2E tests and `tests/conformance/` remain separate infrastructure
boundaries. Active in-process tests run with no Docker or Redis dependency.
boundaries. Active in-process tests run with no Docker or Redis dependency. Resource policy coverage belongs in this active harness, not in ignored binary tests or new legacy-client cases. Runtime unit tests verify that enabling or disabling hooks during a resource read preserves its original policy decision.

Parameter-header integration tests verify that calls without a published tool
schema skip local `Mcp-Param-*` validation and still reach the backend. Unit and
Expand Down
1 change: 1 addition & 0 deletions crates/contextforge-data-plane-cpex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ doctest = false
[dependencies]
arc-swap = "1.7"
async-trait.workspace = true
base64.workspace = true
contextforge-data-plane-apis.workspace = true
cpex.workspace = true
redis.workspace = true
Expand Down
Loading