From 44cb7127d72b1ebbbe925ec9ac77f5682971e2bb Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Tue, 24 Mar 2026 20:51:23 -0700 Subject: [PATCH 01/19] Add RFC-0059: Starlark as programmable middleware for vMCP Proposes extending vMCP's Starlark engine from composite-tool-only scripting into a unified programmable middleware surface, replacing the growing set of independent config knobs (optimizer, filter, rate limiting, PII scrubbing) with a single script per session. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Jeremy Drouillard --- ...V-0059-starlark-programmable-middleware.md | 868 ++++++++++++++++++ 1 file changed, 868 insertions(+) create mode 100644 rfcs/THV-0059-starlark-programmable-middleware.md diff --git a/rfcs/THV-0059-starlark-programmable-middleware.md b/rfcs/THV-0059-starlark-programmable-middleware.md new file mode 100644 index 0000000..d0d554a --- /dev/null +++ b/rfcs/THV-0059-starlark-programmable-middleware.md @@ -0,0 +1,868 @@ +# THV-0059: Starlark as Programmable Middleware for vMCP + +- **Status**: Draft +- **Author(s)**: Jeremy Drouillard (@jerm-dro) +- **Created**: 2026-03-24 +- **Last Updated**: 2026-03-24 +- **Target Repository**: toolhive +- **Related Issues**: [stacklok-epics#213](https://github.com/stacklok/stacklok-epics/issues/213) +- **Supersedes**: [THV-0051 (Starlark Scripted Tools)](./THV-0051-starlark-scripted-tools.md) — this RFC extends THV-0051's scope from composite tool replacement to a unified middleware programming model + +## Summary + +Extend vMCP's Starlark engine from a composite-tool-only scripting layer into a general-purpose programmable middleware surface. A single Starlark script runs once per session. It receives the list of authorized backend tools as `(metadata, handler)` tuples and calls `publish()` to declare what the agent sees — optionally wrapping handlers with additional logic. This replaces the trajectory of adding independent config knobs for each new behavior — knobs that interact in ways that are difficult to predict, test, and explain. + +## Problem Statement + +### Config knob proliferation + +vMCP's feature set is growing. Each feature has arrived with its own configuration surface: + +| Feature | Config surface | Introduced in | +|---------|---------------|---------------| +| Tool advertising filter | `aggregation.tools[].filter`, `excludeAll` | THV-0008 | +| Tool renaming / overrides | `aggregation.tools[].overrides` | THV-0008 | +| Conflict resolution | `aggregation.conflictResolution` | THV-0008 | +| Composite tools | `compositeTools[]`, `compositeToolRefs[]` | THV-0008 | +| Optimizer | `optimizer` (embedding service URL, thresholds, max results) | THV-0022 | +| Starlark scripted tools | `scriptedTools[]`, `scriptedToolRefs[]` | THV-0051 | +| Rate limiting | `rateLimiting.perUser`, `rateLimiting.global`, `rateLimiting.tools[]` | THV-0057 | +| Dynamic webhooks | `validating_webhooks[]`, `mutating_webhooks[]` | THV-0017 | + +Each knob is individually reasonable. The problem is their **interaction**. Today: + +- The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static and doesn't reflect the actual tools available — agents don't know what to search for (see [Slack thread](https://stacklok.slack.com/archives/C09L9QF47EU/p1774392171855569)). +- The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287) (RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is). +- Rate limiting (THV-0057) adds per-tool limits via yet another config block that must reference the same tool names that may have been renamed by overrides. +- There is no mechanism to express cross-cutting policies like "tools without a `readOnly` annotation must only be invokable via a composite tool that includes an elicitation step." + +Every new capability doubles the interaction matrix. Administrators who need non-trivial configurations must understand the ordering and interaction of all these knobs — a burden that scales poorly. + +### The optimizer discoverability problem + +The Slack thread on optimizer quality highlights a concrete symptom: agents don't use `find_tool` because its description doesn't tell them what tools are available behind it. The proposed fix — dynamically generating `find_tool`'s description based on available tools — is a special case of a general need: the ability to programmatically control what the agent sees and how it's described. + +A config knob for "optimizer description template" would fix this one case. But the next request will be "I want the optimizer to group tools by category" or "I want different descriptions per persona." Each becomes another knob. + +### Who is affected + +- **Platform administrators** who configure vMCP for multi-tenant deployments and need predictable behavior from feature combinations. +- **Enterprise integrators** who need custom policies (PII scrubbing, approval workflows, tool restrictions) but don't want to fork ToolHive or maintain webhook services for simple logic. +- **The vMCP development team** who must reason about the interaction of every new feature with every existing feature. + +### Why this is worth solving now + +THV-0051 already introduces Starlark for composite tools. Before that engine ships, we should decide whether Starlark is *only* for composite tools or whether it's the foundation for a unified middleware model. Shipping THV-0051 as-is and then later expanding scope would mean a second migration. + +## Goals + +- Define a Starlark-based programming model that subsumes tool advertising, renaming, optimizer behavior, and composite tool workflows into a single script that runs once per session +- Provide built-in functions for capabilities that would otherwise be config knobs: search indexing, PII scrubbing, rate limiting checks +- Maintain the invariant that **Starlark never sees tools the user is not authorized to use** — Cedar authorization remains the access control boundary +- Make the system accessible to non-power-users via built-in presets that replicate today's config-driven behavior +- Enable policies that span multiple features (e.g., "non-readonly tools require elicitation") + +## Non-Goals + +- Replacing Cedar for authorization decisions — Cedar remains the policy engine for access control +- A general-purpose plugin system for ToolHive beyond vMCP session behavior +- Replacing dynamic webhooks (THV-0017) — webhooks serve the external integration use case; Starlark serves the internal configuration use case +- Moving authentication or transport-level concerns into Starlark +- Supporting multiple scripting languages + +## Proposed Solution + +### High-Level Design + +A vMCP persona runs a single Starlark **middleware script** once per session. The script receives authorized backend tools as `(metadata, handler)` tuples via `tools()`, and calls `publish()` to declare what the agent sees. + +```mermaid +flowchart TB + subgraph "Session Initialization" + Cedar[Cedar Authorization] -->|authorized tools| Engine[Starlark Engine] + Engine -->|run script once| Script[Middleware Script] + Script -->|"publish(meta, fn)"| Published[Published Tool Set] + end + + subgraph "Script Built-ins" + tools_fn["tools() → list of (metadata, handler)"] + publish_fn["publish(metadata, handler)"] + search_fn["search_index(tools) → index"] + scrub_fn["scrub_pii(text) → text"] + rate_fn["check_rate_limit(key, limit, window) → (ok, retry_after)"] + elicit_fn["elicit(message, schema) → decision"] + call_fn["call_tool(name, args) → result"] + end + + subgraph "Runtime" + Agent[MCP Client] -->|tools/list| Published + Agent -->|tools/call| Handler[Published Handler] + Handler -->|backend tools| Backend[Backend MCP Servers] + end + + style Engine fill:#90caf9 + style Cedar fill:#ffcc80 +``` + +**Key invariant**: `tools()` returns only tools the current user is authorized to use. Cedar policies are evaluated *before* the Starlark script runs. The script operates within the authorization boundary, not outside it. + +### The Programming Model + +The script runs once when a session is created. `tools()` returns a list of tuples. Each tuple is `(metadata, handler)`: + +- **`metadata`** is a struct with `name`, `description`, `parameters` (JSON Schema), `annotations` (dict), and `backend_id` +- **`handler`** is a callable `fn(**args) → result` that invokes the backend tool + +`publish(metadata, handler)` adds a tool to the set the agent sees. The handler is called when the agent invokes that tool. + +#### Simplest possible script + +```python +# Publish everything the user is authorized to use. No modification. +for meta, fn in tools(): + publish(meta, fn) +``` + +#### Filtering tools + +```python +for meta, fn in tools(): + if not meta.name.startswith("internal_"): + publish(meta, fn) +``` + +#### Renaming tools + +`metadata` is a simple struct. Create a new one with different fields: + +```python +for meta, fn in tools(): + if meta.name == "pg_query": + publish( + metadata(name="database_query", description="Query the production database", + parameters=meta.parameters, annotations=meta.annotations), + fn, + ) + else: + publish(meta, fn) +``` + +#### Decorating handlers + +Since handlers are just functions, decoration is plain function wrapping: + +```python +def with_pii_scrubbing(fn): + """Wrap a handler to scrub PII from responses.""" + def wrapper(**args): + result = fn(**args) + if "text" in result: + result["text"] = scrub_pii(result["text"]) + return result + return wrapper + +for meta, fn in tools(): + publish(meta, with_pii_scrubbing(fn)) +``` + +Decorators compose naturally: + +```python +for meta, fn in tools(): + wrapped = fn + wrapped = with_rate_limit(wrapped, meta.name) + wrapped = with_pii_scrubbing(wrapped) + + if not meta.annotations.get("readOnly", False): + wrapped = with_approval_gate(wrapped, meta.name) + + publish(meta, wrapped) +``` + +The outermost wrapper runs first. This is just function composition — no special framework. + +#### Defining new tools + +Scripts can create entirely new tools by publishing a `metadata` with a Starlark handler function: + +```python +publish( + metadata( + name="find_tool", + description="Search for tools. Available: " + summary, + parameters=FIND_TOOL_SCHEMA, + ), + lambda query: {"results": index.search(query)}, +) +``` + +### Motivating Use Cases + +#### Use Case 1: Dynamic optimizer descriptions + +**Problem**: Agents don't use `find_tool` because its static description doesn't tell them what's available. + +**Today's solution**: Manual description override or hope the agent figures it out. + +**With programmable middleware**: + +```python +all_tools = tools() +index = search_index(all_tools) + +# Build a dynamic description from actual available tools +by_server = {} +for meta, fn in all_tools: + server = meta.backend_id or "local" + if server not in by_server: + by_server[server] = 0 + by_server[server] += 1 + +desc_parts = ["%s (%d)" % (s, n) for s, n in by_server.items()] +summary = "Search for tools. Available servers: " + ", ".join(desc_parts) + +publish( + metadata(name="find_tool", description=summary, parameters=FIND_TOOL_SCHEMA), + lambda query: {"results": index.search(query)}, +) + +publish( + metadata(name="call_tool", description="Call a tool by name.", + parameters=CALL_TOOL_SCHEMA), + lambda tool_name, arguments: call_tool(tool_name, arguments), +) +``` + +When backends change and the session is recreated, the script re-runs and the description updates. A `tools/list_changed` notification is sent to clients that support it. + +#### Use Case 2: Elicitation gate for write operations + +**Problem**: An administrator wants to ensure that tools capable of mutation are never called without human confirmation. + +**Today's solution**: Not possible without writing a custom composite tool wrapper for every write tool. + +**With programmable middleware**: + +```python +def with_approval_gate(fn, tool_name): + def wrapper(**args): + decision = elicit( + "Tool '%s' may modify data. Approve?" % tool_name, + schema={"type": "object", "properties": {"reason": {"type": "string"}}}, + ) + if decision.action != "accept": + return {"error": "Declined by user"} + return fn(**args) + return wrapper + +for meta, fn in tools(): + if not meta.annotations.get("readOnly", False): + fn = with_approval_gate(fn, meta.name) + publish(meta, fn) +``` + +A single policy, applied once, covering all tools. + +#### Use Case 3: PII scrubbing + +**Problem**: Tool responses may contain PII that should be redacted before reaching the agent. + +**Today's solution**: Requires a mutating webhook (THV-0017) calling an external service. + +**With programmable middleware**: + +```python +def with_pii_scrubbing(fn): + def wrapper(**args): + result = fn(**args) + if "text" in result: + result["text"] = scrub_pii(result["text"]) + return result + return wrapper + +for meta, fn in tools(): + publish(meta, with_pii_scrubbing(fn)) +``` + +`scrub_pii()` is a Go-implemented built-in that applies regex-based and NER-based entity detection. It handles common patterns (emails, phone numbers, SSNs, credit cards) without requiring an external service. + +#### Use Case 4: Tool aggregation and renaming + +**Problem**: An administrator wants to present a curated set of tools — renaming some, hiding others, grouping related tools under a single facade. + +**Today's solution**: `aggregation.tools[].overrides` for renaming, `aggregation.tools[].filter` / `excludeAll` for hiding. + +**With programmable middleware**: + +```python +for meta, fn in tools(): + # Hide internal tools + if meta.name.startswith("internal_"): + continue + + # Rename for clarity + if meta.name == "pg_query": + publish( + metadata(name="database_query", description="Query the production database", + parameters=meta.parameters, annotations=meta.annotations), + fn, + ) + continue + + # Skip Jira tools — we'll group them below + if meta.name in ["jira_create", "jira_update", "jira_search"]: + continue + + publish(meta, fn) + +# Publish a composite Jira tool +def jira_handler(action, **args): + if action == "create": + return call_tool("jira_create", args) + elif action == "update": + return call_tool("jira_update", args) + elif action == "search": + return call_tool("jira_search", args) + +publish( + metadata(name="jira", description="Manage Jira issues: create, update, or search", + parameters=JIRA_SCHEMA), + jira_handler, +) +``` + +#### Use Case 5: Rate limiting with context-aware policies + +**Problem**: Rate limits need to vary by tool sensitivity and user role. + +**Today's solution**: THV-0057 provides static `requestsPerWindow` / `windowSeconds` per tool. + +**With programmable middleware**: + +```python +LIMITS = { + "admin": {"default": 1000, "expensive_search": 100}, + "standard": {"default": 100, "expensive_search": 10}, +} + +def with_rate_limit(fn, tool_name): + def wrapper(**args): + user = current_user() + role = user.groups[0] if user.groups else "standard" + role_limits = LIMITS.get(role, LIMITS["standard"]) + limit = role_limits.get(tool_name, role_limits["default"]) + + allowed, retry_after = check_rate_limit( + key=user.sub + ":" + tool_name, limit=limit, window=60, + ) + if not allowed: + return {"error": "Rate limited", "retry_after": retry_after} + return fn(**args) + return wrapper + +for meta, fn in tools(): + publish(meta, with_rate_limit(fn, meta.name)) +``` + +`check_rate_limit()` is backed by the same Redis token bucket from THV-0057. The *policy* is expressed in Starlark; the *mechanism* lives in Go. + +#### Use Case 6: Composing multiple concerns + +A single script handles optimizer + elicitation gate + PII scrubbing + rate limiting — behaviors that today require four different config surfaces: + +```python +all_tools = tools() +index = search_index(all_tools) +desc = build_summary(all_tools) + +# Compose decorators for the call_tool dispatch path +def dispatch(tool_name, arguments): + user = current_user() + + # Rate limit + allowed, retry_after = check_rate_limit( + key=user.sub + ":" + tool_name, limit=100, window=60, + ) + if not allowed: + return {"error": "Rate limited", "retry_after": retry_after} + + # Elicitation gate for non-readonly tools + t = get_tool(tool_name) + if t and not t.annotations.get("readOnly", False): + decision = elicit("Approve call to '%s'?" % tool_name) + if decision.action != "accept": + return {"error": "Declined"} + + # Execute and scrub + result = call_tool(tool_name, arguments) + if "text" in result: + result["text"] = scrub_pii(result["text"]) + return result + +publish( + metadata(name="find_tool", description=desc, parameters=FIND_TOOL_SCHEMA), + lambda query: {"results": index.search(query)}, +) + +publish( + metadata(name="call_tool", description="Call a tool by name.", + parameters=CALL_TOOL_SCHEMA), + dispatch, +) + +def build_summary(tool_list): + cats = {} + for meta, fn in tool_list: + cat = meta.annotations.get("category", "general") + if cat not in cats: + cats[cat] = [] + cats[cat].append(meta.name) + return "Search for tools across: " + ", ".join( + "%s (%d tools)" % (c, len(ns)) for c, ns in cats.items() + ) +``` + +The ordering is explicit. The interactions are visible. There are no surprising feature interactions because the administrator wrote the interaction. + +### Built-in Functions + +These are Go-implemented functions exposed to Starlark scripts. + +#### Tool enumeration and publishing + +| Built-in | Signature | Description | +|----------|-----------|-------------| +| `tools()` | `tools() → list[(metadata, handler)]` | Returns all authorized backend tools as `(metadata, handler)` tuples. `metadata` is a struct with `name`, `description`, `parameters`, `annotations`, `backend_id`. `handler` is a callable that invokes the backend. | +| `publish(meta, handler)` | `publish(metadata, callable) → None` | Adds a tool to the set visible to the agent. | +| `metadata(...)` | `metadata(name, description, parameters=None, annotations=None) → metadata` | Creates a new metadata struct. Used when renaming or defining new tools. | +| `get_tool(name)` | `get_tool(name) → metadata or None` | Looks up a specific authorized tool's metadata by name. | + +#### Tool call execution + +| Built-in | Signature | Description | +|----------|-----------|-------------| +| `call_tool(name, args)` | `call_tool(name, dict) → dict` | Calls a backend tool by name. Halts on error. | +| `try_call_tool(name, args)` | `try_call_tool(name, dict) → struct(ok, error, output)` | Calls a backend tool. Returns error info instead of halting. | +| `retry(fn, max_attempts, delay)` | `retry(fn, max_attempts=3, delay="1s") → any` | Retries a callable with exponential backoff. | +| `parallel(fns)` | `parallel(fns) → list` | Executes zero-argument callables concurrently. | + +#### Middleware capabilities + +| Built-in | Signature | Description | +|----------|-----------|-------------| +| `search_index(tools)` | `search_index(list[(metadata, handler)]) → SearchIndex` | Builds a semantic search index over the tool list. Returns an object with `.search(query) → list[dict]`. | +| `scrub_pii(text)` | `scrub_pii(text) → string` | Redacts PII patterns (emails, phones, SSNs, credit cards) from text. | +| `check_rate_limit(key, limit, window)` | `check_rate_limit(key, limit, window) → (bool, int)` | Checks a token bucket counter in Redis. Returns `(allowed, retry_after_seconds)`. | +| `elicit(message, schema)` | `elicit(message, schema={}) → struct(action, content)` | Prompts the user for a decision via MCP elicitation. | +| `current_user()` | `current_user() → struct(sub, email, groups)` | Returns the authenticated user's identity. | +| `log(message)` | `log(message) → None` | Emits a structured audit log entry. | + +### Presets: Making it Easy for Non-Power-Users + +The critical question is: how do people who don't want to write Starlark still use vMCP? + +**Answer: presets.** A preset is a named, built-in Starlark script that replicates the behavior of today's config knobs. Existing config fields become parameters to the preset. + +Today's `Config` struct has top-level fields: `aggregation`, `compositeTools`, `compositeToolRefs`, and `optimizer`. The new `middleware` field sits alongside them: + +```go +type Config struct { + // ... existing fields unchanged ... + Aggregation *AggregationConfig `json:"aggregation,omitempty"` + CompositeTools []CompositeToolConfig `json:"compositeTools,omitempty"` + Optimizer *OptimizerConfig `json:"optimizer,omitempty"` + + // New field + Middleware *MiddlewareConfig `json:"middleware,omitempty"` +} +``` + +When `middleware` is set, it takes precedence over `aggregation`, `compositeTools`, and `optimizer`. When it is absent, those fields continue to work exactly as today — no behavior change for existing deployments. + +#### Config surface + +```yaml +# Option 1: Use a preset (maps to existing config patterns) +middleware: + preset: "standard" + +# Option 2: Preset with parameters (replacing today's config knobs) +middleware: + preset: "optimizer" + config: + optimizer: + embeddingService: "http://embedding-server:8080" + maxToolsToReturn: 8 + hybridSearchSemanticRatio: "0.5" + aggregation: + tools: + - workload: "backend-a" + excludeAll: true + - workload: "backend-b" + filter: ["search", "query"] + overrides: + search: + name: "global_search" + description: "Search across all sources" + piiScrubbing: + enabled: true + rateLimiting: + perUser: + requestsPerWindow: 100 + windowSeconds: 60 + +# Option 3: Custom script (power users) +middleware: + script: | + for meta, fn in tools(): + publish(meta, fn) + +# Option 4: External script file +middleware: + scriptFile: "middleware/policy.star" +``` + +The `config` block under a preset accepts the same structure as today's top-level config fields (`aggregation`, `optimizer`) plus new fields (`piiScrubbing`, `rateLimiting`). The preset script reads these via `config()` and translates them into the appropriate `publish()` calls and handler decorations. + +#### Built-in presets + +| Preset | Behavior | Today's equivalent | +|--------|----------|--------------------| +| `passthrough` | Publishes all authorized tools unmodified. | No `aggregation`, no `optimizer` | +| `standard` | Applies filtering, renaming, conflict resolution, and optional rate limiting from `config`. | `aggregation` + `compositeTools` | +| `optimizer` | Publishes `find_tool` / `call_tool` with dynamic descriptions, applying filtering/renaming from `config`. Supports PII scrubbing and rate limiting. | `aggregation` + `optimizer` | + +Users can inspect what a preset does: + +```bash +thv vmcp show-preset optimizer +``` + +This prints the Starlark source, making the preset transparent and forkable. A user who needs 90% of a preset's behavior can copy it and modify the 10% they need. + +#### Migration path + +When no `middleware` block is present but `aggregation`, `compositeTools`, or `optimizer` fields exist, vMCP behaves exactly as today — the existing code paths run. No deprecation, no behavior change. + +When a user wants to adopt programmable middleware, they add a `middleware` block. At that point, `aggregation`, `compositeTools`, and `optimizer` are ignored (if both are present, vMCP logs a warning). A `thv vmcp migrate-config` command generates the equivalent `middleware` block from the existing config. + +### Detailed Design + +#### Script lifecycle + +```mermaid +sequenceDiagram + participant Factory as Session Factory + participant Engine as Starlark Engine + participant Cedar as Cedar Authz + participant Script as Middleware Script + participant Agent as MCP Client + + Note over Factory: Session creation + Factory->>Engine: Load script (preset or custom) + Engine->>Engine: Parse and validate + + Factory->>Cedar: Determine authorized tools for user + Cedar-->>Factory: Authorized tool set + + Factory->>Engine: Execute script with authorized tools + Engine->>Script: Run top-level script body + Script->>Script: tools() → iterate, filter, wrap, publish + Script-->>Engine: Published (metadata, handler) set + + Note over Agent: tools/list + Agent->>Factory: tools/list + Factory-->>Agent: Published tool metadata + + Note over Agent: tools/call + Agent->>Factory: tools/call "find_tool" {query: "github"} + Factory->>Engine: Invoke published handler for "find_tool" + Engine->>Script: handler(query="github") + Script-->>Engine: {results: [...]} + Engine-->>Agent: CallToolResult +``` + +The script runs **once** per session, not per request. `publish()` calls build up the tool set. Handlers are stored and invoked later when the agent makes `tools/call` requests. + +#### Where this fits in the architecture + +The Starlark middleware replaces the current decorator stack for tool-level concerns: + +``` +Current decorator stack: New model: + + optimizer decorator Starlark middleware + filter decorator (subsumes all of these) + composite tools decorator + base session base session +``` + +The base session's routing table, conflict resolution, and backend name reversal (from RFC-0058) remain unchanged. The Starlark middleware sees post-resolution tool names. + +Concretely, the Starlark engine is a single session decorator that: +- Runs the script at session creation, collecting `publish()` calls +- Returns published tool metadata for `Tools()` calls +- Dispatches `CallTool()` to the published handler for the requested tool + +#### Interaction with Cedar authorization + +Cedar policies operate at the HTTP middleware layer. The Starlark engine receives only authorized tools: + +1. Authentication middleware extracts user identity +2. Cedar middleware evaluates policies, determines authorized tools +3. Session layer receives authorized tool set +4. `tools()` returns only tools that passed Cedar +5. `call_tool()` delegates to the base session, which enforces the routing table + +If a script attempts `call_tool("secret_admin_tool", ...)` for an unauthorized user, the base session rejects it. The script cannot escalate privileges. + +#### Interaction with dynamic webhooks + +Webhooks (THV-0017) and Starlark middleware serve different purposes at different layers: + +- **Webhooks** integrate **external systems** at the HTTP middleware layer +- **Starlark** configures **vMCP-internal behavior** at the session layer + +Both coexist. A request passes through webhooks first (external policy), then reaches the Starlark-published handler (internal routing). + +#### Interaction with rate limiting + +THV-0057's Redis-backed token bucket is the *mechanism*. `check_rate_limit()` exposes it to scripts. The *policy* can be: + +1. **Config-driven**: The `standard` / `optimizer` presets read `rateLimiting` from `config` and call `check_rate_limit()` internally +2. **Script-driven**: Custom scripts implement context-aware rate limiting + +### API Changes + +#### New config fields + +```go +type MiddlewareConfig struct { + // Preset is a named built-in middleware script. + // One of: "passthrough", "standard", "optimizer". + Preset string `json:"preset,omitempty" yaml:"preset,omitempty"` + + // Config is passed to the preset script via config(). + // Accepts the same structure as today's aggregation, optimizer, etc. + Config *MiddlewarePresetConfig `json:"config,omitempty" yaml:"config,omitempty"` + + // Script is inline Starlark source. Mutually exclusive with Preset and ScriptFile. + Script string `json:"script,omitempty" yaml:"script,omitempty"` + + // ScriptFile is a path to a .star file. Mutually exclusive with Preset and Script. + ScriptFile string `json:"scriptFile,omitempty" yaml:"scriptFile,omitempty"` +} + +type MiddlewarePresetConfig struct { + Aggregation *AggregationConfig `json:"aggregation,omitempty"` + Optimizer *OptimizerConfig `json:"optimizer,omitempty"` + PIIScrubbing *PIIScrubConfig `json:"piiScrubbing,omitempty"` + RateLimiting *RateLimitConfig `json:"rateLimiting,omitempty"` +} +``` + +#### Existing config fields: no change + +`aggregation`, `compositeTools`, `compositeToolRefs`, and `optimizer` remain on `Config` and work exactly as today when `middleware` is absent. When `middleware` is present, they are ignored (with a warning if both exist). + +#### New CRD + +`VirtualMCPMiddlewareScript` — references a Starlark middleware script from a ConfigMap: + +```yaml +apiVersion: toolhive.stacklok.com/v1alpha1 +kind: VirtualMCPMiddlewareScript +metadata: + name: my-org-middleware +spec: + configMapRef: + name: vmcp-middleware-scripts + key: policy.star +``` + +## Security Considerations + +### Threat Model + +| Threat | Description | Severity | +|--------|-------------|----------| +| **Privilege escalation via script** | Script calls `call_tool()` for an unauthorized tool | High | +| **Denial of service via infinite loop** | Script with `while True` or deep recursion | High | +| **Tool list manipulation** | Script publishes tools that shouldn't be visible | Medium | +| **Decorator bypass** | Script omits `scrub_pii()` or `check_rate_limit()` | Medium | +| **Resource exhaustion** | Script builds large data structures | High | + +### Authentication and Authorization + +**Cedar remains the authorization boundary.** The Starlark engine cannot circumvent it: + +- `tools()` returns only Cedar-authorized tools +- `call_tool()` delegates to the base session's `CallTool()`, which checks the routing table built from Cedar-authorized tools only +- `publish()` can publish tools from `tools()` or new tools whose handlers use `call_tool()` — which is Cedar-gated + +**Trust model**: Middleware scripts are written by administrators, not end users. An administrator who can write a Starlark script already has the authority to configure vMCP. + +### Data Security + +- Scripts cannot access filesystem, network, or environment variables (Starlark sandbox) +- `scrub_pii()` operates on the Go side with auditable patterns +- Tool call results transit through handlers; administrators are trusted (same model as webhook config) + +### Input Validation + +- Scripts are parsed and validated at config load time +- `publish()` validates metadata (non-empty name, valid JSON Schema) +- Built-in arguments are validated in Go + +### Secrets Management + +Scripts have no access to secrets. Backend authentication is handled below the script's view. + +### Audit and Logging + +- Each `publish()` logged (tool name, source: backend or script-defined) +- Each handler invocation logged (tool name, duration, outcome) +- Each `check_rate_limit()` logged (key, limit, decision) +- Each `scrub_pii()` logged (redaction count) +- Each `elicit()` logged (prompt, action, duration) + +### Mitigations + +| Threat | Mitigation | +|--------|-----------| +| Privilege escalation | `tools()` and `call_tool()` are Cedar-gated | +| DoS via loops | Execution step limit (default 1M), context timeout (same as THV-0051) | +| Tool list manipulation | `publish()` only surfaces tools from `tools()` or script-defined tools; audit logs record every call | +| Decorator bypass | Presets include scrubbing/rate limiting when configured; custom scripts are admin's responsibility | +| Resource exhaustion | Execution step limit, memory monitoring (same as THV-0051) | + +## Alternatives Considered + +### Alternative 1: Keep adding config knobs + +- **Pros**: No new concepts for simple cases +- **Cons**: Interaction matrix grows quadratically. Bugs like #4287 from non-obvious interactions. Testing becomes intractable. +- **Why not chosen**: Already causing problems at current feature count. + +### Alternative 2: Starlark for composite tools only (THV-0051 as-is) + +- **Pros**: Smaller scope +- **Cons**: Misses the opportunity to unify. Interaction problem remains for optimizer + filter + rate limiting. Expanding scope later means a second migration. +- **Why not chosen**: Design for the broader use case from day one. + +### Alternative 3: Use webhooks for everything + +- **Pros**: Maximum flexibility, language-agnostic +- **Cons**: External services for simple policies. Network latency on every call. Overkill for "hide these tools." +- **Why not chosen**: Webhooks for external integration, Starlark for internal configuration. Both should exist. + +### Alternative 4: OPA / Rego instead of Starlark + +- **Pros**: Established policy language +- **Cons**: Rego is for boolean decisions (allow/deny), not programmatic composition. Expressing "publish a search tool with a dynamic description" would be extremely awkward. We already use Cedar for authz. +- **Why not chosen**: Wrong abstraction — we need a programming model, not a policy language. + +## Compatibility + +### Backward Compatibility + +All existing config fields continue to work unchanged. `middleware` is a new, optional field. When absent, existing code paths run. No deprecation of existing fields in this RFC — they remain first-class until the ecosystem has adopted programmable middleware. + +When `middleware` is present, `aggregation`, `compositeTools`, and `optimizer` are ignored. If both are set, vMCP logs a warning. + +### Forward Compatibility + +New built-in functions can be added without breaking existing scripts. New presets can be added alongside existing ones. The `config` map on presets uses the same types as existing config, so new config fields are automatically available. + +## Implementation Plan + +### Phase 1: Core engine — `tools()`, `publish()`, `metadata()` + +- Extend the Starlark engine from THV-0051 with `tools()`, `publish()`, `metadata()`, `get_tool()` built-ins +- Implement the session decorator that collects `publish()` calls and dispatches `CallTool()` to handlers +- Implement `passthrough` preset +- Config model: `middleware.preset`, `middleware.script`, `middleware.scriptFile` +- Unit tests, integration test for tool publishing and handler dispatch + +### Phase 2: Built-in capabilities and presets + +- Port `search_index()` from current optimizer implementation +- Implement `scrub_pii()` built-in +- Implement `check_rate_limit()` built-in (backed by THV-0057's Redis token bucket) +- Implement `standard` and `optimizer` presets with `config` parameter support +- Integration tests for each built-in, E2E tests for presets in K8s + +### Phase 3: Migration tooling + +- Implement `thv vmcp migrate-config` CLI command +- Implement `thv vmcp show-preset` command +- Port existing tests to validate preset equivalence with old config paths +- Documentation + +### Phase 4: Cleanup (future) + +- Remove optimizer, filter, and composite tools decorators +- Consolidated test suite + +### Dependencies + +- THV-0051 (Starlark engine core) — base engine, value converter, `call_tool`, `try_call_tool`, `retry`, `parallel`, `elicit`, `log` +- THV-0058 (aggregator decomposition) — clean base session for the decorator to sit on +- THV-0057 (rate limiting) — Redis token bucket for `check_rate_limit()` + +## Testing Strategy + +- **Unit tests**: Each built-in in isolation. Handler wrapping / function composition. `publish()` validation. Preset loading and config injection. +- **Integration tests**: Full script execution with mock backends. Decorator chains. Composite tool handlers via `call_tool()`. Optimizer pattern with `search_index()`. +- **E2E tests**: Preset configuration in K8s. Custom scripts via ConfigMap. Old config → middleware migration. +- **Security tests**: `tools()` respects Cedar. `call_tool()` rejects unauthorized tools. Step limits. Memory. +- **Preset equivalence tests**: For each preset, verify behavior matches the old config-driven feature it replaces. + +## Documentation + +- **User guide**: Writing middleware scripts, built-in reference, decorator patterns +- **Preset reference**: What each preset does, parameters, `show-preset` and forking +- **Migration guide**: From old config knobs to middleware presets or custom scripts +- **Architecture docs**: Updated vMCP architecture with middleware model +- **CRD reference**: `VirtualMCPMiddlewareScript` + +## Open Questions + +1. **Handler argument passing**: Should handlers receive keyword arguments (`fn(**args)`) or a single dict (`fn(args)`)? Keyword args are more Pythonic but Starlark's `**kwargs` support varies. A single dict matches `call_tool(name, args)` and is simpler. + +2. **Should presets be composable?** Could a user layer multiple presets, or is a single preset + config sufficient? Multiple presets add complexity in ordering and config conflicts. + +3. **Hot reloading**: Should ConfigMap updates to scripts trigger live session recreation? Convenient but complex (re-validation, in-flight calls). + +4. **Interaction with existing `compositeTools` during migration**: Should middleware scripts be able to `load()` composite tool definitions from CRDs, or must everything be consolidated into the script? + +5. **Custom PII patterns**: Should `scrub_pii()` accept custom regex patterns (e.g., internal employee ID formats), or is the built-in set sufficient? + +6. **Thread safety of `parallel()`**: When `parallel()` invokes handlers that themselves call `call_tool()`, each goroutine needs its own Starlark thread. The engine must ensure published handlers are safe to call concurrently. + +## References + +- [THV-0051: Starlark Scripted Tools](./THV-0051-starlark-scripted-tools.md) — original Starlark RFC +- [THV-0058: Inline Aggregator, Extract Filter Decorator](./THV-0058-inline-aggregator-filter-decorator.md) — aggregator decomposition +- [THV-0057: Rate Limiting](./THV-0057-rate-limiting.md) — rate limiting mechanism +- [THV-0017: Dynamic Webhook Middleware](./THV-0017-dynamic-webhook-middleware.md) — external webhook integration +- [stacklok-epics#213](https://github.com/stacklok/stacklok-epics/issues/213) — Dynamic Webhook Middleware epic +- [Optimizer discoverability discussion](https://stacklok.slack.com/archives/C09L9QF47EU/p1774392171855569) — Slack thread +- [Starlark Language Specification](https://github.com/bazelbuild/starlark/blob/master/spec.md) +- [starlark-go Implementation](https://github.com/google/starlark-go) + +--- + +## RFC Lifecycle + +### Review History + +| Date | Reviewer | Decision | Notes | +|------|----------|----------|-------| +| 2026-03-24 | @jerm-dro | Draft | Initial submission | + +### Implementation Tracking + +| Repository | PR | Status | +|------------|-----|--------| +| toolhive | TBD | Not started | From aa79671145454d81609a2b4e1e9ca3ffc9264b56 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 25 Mar 2026 09:24:51 -0700 Subject: [PATCH 02/19] Address review comments on RFC-0059 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename "middleware script" → "session initialization script" throughout - backends() returns dict[string, Backend] instead of flat tools() list - Handler functions take single dict arg instead of **kwargs - metadata() requires all fields (name, description, parameters, annotations) - Rewrite presets section: no config nesting, auto-generation from existing config - Update sequence diagram: remove Starlark Engine, use MultiSession - Restructure implementation phases: feature parity first, then new capabilities - Simplify Cedar interaction section - Remove THV-0058 reference - Update open questions (remove resolved, add error handling) - Rename CRD to VirtualMCPSessionInitScript - Rename config struct to SessionInitConfig Co-Authored-By: Claude Opus 4.6 --- ...V-0059-starlark-programmable-middleware.md | 527 ++++++++---------- 1 file changed, 241 insertions(+), 286 deletions(-) diff --git a/rfcs/THV-0059-starlark-programmable-middleware.md b/rfcs/THV-0059-starlark-programmable-middleware.md index d0d554a..bca3e20 100644 --- a/rfcs/THV-0059-starlark-programmable-middleware.md +++ b/rfcs/THV-0059-starlark-programmable-middleware.md @@ -1,22 +1,22 @@ -# THV-0059: Starlark as Programmable Middleware for vMCP +# THV-0059: Starlark Session Initialization for vMCP - **Status**: Draft - **Author(s)**: Jeremy Drouillard (@jerm-dro) - **Created**: 2026-03-24 -- **Last Updated**: 2026-03-24 +- **Last Updated**: 2026-03-25 - **Target Repository**: toolhive - **Related Issues**: [stacklok-epics#213](https://github.com/stacklok/stacklok-epics/issues/213) -- **Supersedes**: [THV-0051 (Starlark Scripted Tools)](./THV-0051-starlark-scripted-tools.md) — this RFC extends THV-0051's scope from composite tool replacement to a unified middleware programming model +- **Related**: [THV-0051 (Starlark Scripted Tools)](./THV-0051-starlark-scripted-tools.md) — this RFC broadens the scope of Starlark in vMCP from composite tool workflows to a unified session initialization model ## Summary -Extend vMCP's Starlark engine from a composite-tool-only scripting layer into a general-purpose programmable middleware surface. A single Starlark script runs once per session. It receives the list of authorized backend tools as `(metadata, handler)` tuples and calls `publish()` to declare what the agent sees — optionally wrapping handlers with additional logic. This replaces the trajectory of adding independent config knobs for each new behavior — knobs that interact in ways that are difficult to predict, test, and explain. +Introduce a Starlark-based session initialization script for vMCP. A single script runs once per session, receives the authorized backends and their capabilities, and calls `publish()` to declare what the agent sees — optionally wrapping handlers with additional logic. This replaces the growing set of independent config knobs (aggregation, optimizer, filtering, rate limiting) whose combinations interact in ways that are difficult to predict, test, and explain. ## Problem Statement -### Config knob proliferation +### Config knob combinations -vMCP's feature set is growing. Each feature has arrived with its own configuration surface: +vMCP's feature set is growing. Each feature has arrived with its own configuration surface. The problem is not just the number of knobs, but that they have subtle dependencies on each other: conflict resolution and aggregation change tool names, filtering changes which tools are available at different points in the pipeline, and downstream config blocks (rate limiting, composite tools) must reference tool names that earlier config blocks may have renamed or removed. The result is that configuring one feature correctly requires understanding the side effects of every other feature: | Feature | Config surface | Introduced in | |---------|---------------|---------------| @@ -25,13 +25,13 @@ vMCP's feature set is growing. Each feature has arrived with its own configurati | Conflict resolution | `aggregation.conflictResolution` | THV-0008 | | Composite tools | `compositeTools[]`, `compositeToolRefs[]` | THV-0008 | | Optimizer | `optimizer` (embedding service URL, thresholds, max results) | THV-0022 | -| Starlark scripted tools | `scriptedTools[]`, `scriptedToolRefs[]` | THV-0051 | -| Rate limiting | `rateLimiting.perUser`, `rateLimiting.global`, `rateLimiting.tools[]` | THV-0057 | -| Dynamic webhooks | `validating_webhooks[]`, `mutating_webhooks[]` | THV-0017 | +| Starlark scripted tools | `scriptedTools[]`, `scriptedToolRefs[]` | THV-0051 (proposed) | +| Rate limiting | `rateLimiting.perUser`, `rateLimiting.global`, `rateLimiting.tools[]` | THV-0057 (proposed) | +| Dynamic webhooks | `validating_webhooks[]`, `mutating_webhooks[]` | THV-0017 (proposed) | Each knob is individually reasonable. The problem is their **interaction**. Today: -- The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static and doesn't reflect the actual tools available — agents don't know what to search for (see [Slack thread](https://stacklok.slack.com/archives/C09L9QF47EU/p1774392171855569)). +- The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static — agents don't know what tools they might find behind it (see [Slack thread](https://stacklok.slack.com/archives/C09L9QF47EU/p1774392171855569)). - The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287) (RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is). - Rate limiting (THV-0057) adds per-tool limits via yet another config block that must reference the same tool names that may have been renamed by overrides. - There is no mechanism to express cross-cutting policies like "tools without a `readOnly` annotation must only be invokable via a composite tool that includes an elicitation step." @@ -52,7 +52,7 @@ A config knob for "optimizer description template" would fix this one case. But ### Why this is worth solving now -THV-0051 already introduces Starlark for composite tools. Before that engine ships, we should decide whether Starlark is *only* for composite tools or whether it's the foundation for a unified middleware model. Shipping THV-0051 as-is and then later expanding scope would mean a second migration. +THV-0051 proposes Starlark for composite tools. Before that engine ships, we should decide whether Starlark is *only* for composite tools or whether it's the foundation for a unified session initialization model. Shipping THV-0051 as-is and then later expanding scope would mean a second migration. ## Goals @@ -61,6 +61,7 @@ THV-0051 already introduces Starlark for composite tools. Before that engine shi - Maintain the invariant that **Starlark never sees tools the user is not authorized to use** — Cedar authorization remains the access control boundary - Make the system accessible to non-power-users via built-in presets that replicate today's config-driven behavior - Enable policies that span multiple features (e.g., "non-readonly tools require elicitation") +- Maintain full backward compatibility with existing config fields — the session initialization script must be able to replicate every behavior currently achievable via `aggregation`, `optimizer`, and related config (except legacy composite tools, which are replaced by Starlark scripts from THV-0051) ## Non-Goals @@ -74,18 +75,18 @@ THV-0051 already introduces Starlark for composite tools. Before that engine shi ### High-Level Design -A vMCP persona runs a single Starlark **middleware script** once per session. The script receives authorized backend tools as `(metadata, handler)` tuples via `tools()`, and calls `publish()` to declare what the agent sees. +A vMCP persona runs a single Starlark **session initialization script** once per session. The script receives the authorized backends via `backends()` — a dict keyed by backend name, where each value exposes the backend's tools, resources, and prompts. The script calls `publish()` to declare what the agent sees. ```mermaid flowchart TB subgraph "Session Initialization" - Cedar[Cedar Authorization] -->|authorized tools| Engine[Starlark Engine] - Engine -->|run script once| Script[Middleware Script] - Script -->|"publish(meta, fn)"| Published[Published Tool Set] + Cedar[Cedar Authorization] -->|authorized backends| Factory[Session Factory] + Factory -->|run script once| Script[Session Init Script] + Script -->|"publish(meta, fn)"| Session[MultiSession] end subgraph "Script Built-ins" - tools_fn["tools() → list of (metadata, handler)"] + backends_fn["backends() → dict of backend objects"] publish_fn["publish(metadata, handler)"] search_fn["search_index(tools) → index"] scrub_fn["scrub_pii(text) → text"] @@ -95,23 +96,29 @@ flowchart TB end subgraph "Runtime" - Agent[MCP Client] -->|tools/list| Published + Agent[MCP Client] -->|tools/list| Session Agent -->|tools/call| Handler[Published Handler] Handler -->|backend tools| Backend[Backend MCP Servers] end - style Engine fill:#90caf9 + style Factory fill:#90caf9 style Cedar fill:#ffcc80 ``` -**Key invariant**: `tools()` returns only tools the current user is authorized to use. Cedar policies are evaluated *before* the Starlark script runs. The script operates within the authorization boundary, not outside it. +**Key invariant**: `backends()` returns only backends and capabilities the current user is authorized to use. Cedar policies are evaluated *before* the script runs. The script operates within the authorization boundary, not outside it. ### The Programming Model -The script runs once when a session is created. `tools()` returns a list of tuples. Each tuple is `(metadata, handler)`: +The script runs once when a session is created. `backends()` returns a dict keyed by backend name. Each backend object exposes: + +- **`backend.tools()`** — returns a list of `(metadata, handler)` tuples for the backend's tools +- **`backend.resources()`** — returns the backend's resources (future use) +- **`backend.prompts()`** — returns the backend's prompts (future use) + +Each tool tuple is `(metadata, handler)`: - **`metadata`** is a struct with `name`, `description`, `parameters` (JSON Schema), `annotations` (dict), and `backend_id` -- **`handler`** is a callable `fn(**args) → result` that invokes the backend tool +- **`handler`** is a callable `fn(args) → result` that invokes the backend tool. `args` is a dict of named arguments. `publish(metadata, handler)` adds a tool to the set the agent sees. The handler is called when the agent invokes that tool. @@ -119,32 +126,53 @@ The script runs once when a session is created. `tools()` returns a list of tupl ```python # Publish everything the user is authorized to use. No modification. -for meta, fn in tools(): - publish(meta, fn) +for name, backend in backends().items(): + for meta, fn in backend.tools(): + publish(meta, fn) ``` #### Filtering tools ```python -for meta, fn in tools(): - if not meta.name.startswith("internal_"): +for name, backend in backends().items(): + for meta, fn in backend.tools(): + if not meta.name.startswith("internal_"): + publish(meta, fn) +``` + +#### Handling name collisions across backends + +Because the script sees which backend each tool comes from, it can handle collisions explicitly — no need for a separate `conflictResolution` config: + +```python +for name, backend in backends().items(): + for meta, fn in backend.tools(): + # Prefix tools from all backends except the primary + if name != "primary": + meta = metadata( + name = name + "_" + meta.name, + description = meta.description, + parameters = meta.parameters, + annotations = meta.annotations, + ) publish(meta, fn) ``` #### Renaming tools -`metadata` is a simple struct. Create a new one with different fields: +`metadata` is a simple struct. To rename, create a new metadata explicitly passing all fields — `annotations` and `parameters` are required to prevent accidentally dropping them: ```python -for meta, fn in tools(): - if meta.name == "pg_query": - publish( - metadata(name="database_query", description="Query the production database", - parameters=meta.parameters, annotations=meta.annotations), - fn, - ) - else: - publish(meta, fn) +for name, backend in backends().items(): + for meta, fn in backend.tools(): + if meta.name == "pg_query": + publish( + metadata(name="database_query", description="Query the production database", + parameters=meta.parameters, annotations=meta.annotations), + fn, + ) + else: + publish(meta, fn) ``` #### Decorating handlers @@ -154,29 +182,31 @@ Since handlers are just functions, decoration is plain function wrapping: ```python def with_pii_scrubbing(fn): """Wrap a handler to scrub PII from responses.""" - def wrapper(**args): - result = fn(**args) + def wrapper(args): + result = fn(args) if "text" in result: result["text"] = scrub_pii(result["text"]) return result return wrapper -for meta, fn in tools(): - publish(meta, with_pii_scrubbing(fn)) +for name, backend in backends().items(): + for meta, fn in backend.tools(): + publish(meta, with_pii_scrubbing(fn)) ``` Decorators compose naturally: ```python -for meta, fn in tools(): - wrapped = fn - wrapped = with_rate_limit(wrapped, meta.name) - wrapped = with_pii_scrubbing(wrapped) +for name, backend in backends().items(): + for meta, fn in backend.tools(): + wrapped = fn + wrapped = with_rate_limit(wrapped, meta.name) + wrapped = with_pii_scrubbing(wrapped) - if not meta.annotations.get("readOnly", False): - wrapped = with_approval_gate(wrapped, meta.name) + if not meta.annotations.get("readOnly", False): + wrapped = with_approval_gate(wrapped, meta.name) - publish(meta, wrapped) + publish(meta, wrapped) ``` The outermost wrapper runs first. This is just function composition — no special framework. @@ -191,8 +221,9 @@ publish( name="find_tool", description="Search for tools. Available: " + summary, parameters=FIND_TOOL_SCHEMA, + annotations={}, ), - lambda query: {"results": index.search(query)}, + lambda args: {"results": index.search(args["query"])}, ) ``` @@ -204,32 +235,33 @@ publish( **Today's solution**: Manual description override or hope the agent figures it out. -**With programmable middleware**: +**With session initialization script**: ```python -all_tools = tools() +all_tools = [] +for name, backend in backends().items(): + all_tools += backend.tools() + index = search_index(all_tools) -# Build a dynamic description from actual available tools -by_server = {} -for meta, fn in all_tools: - server = meta.backend_id or "local" - if server not in by_server: - by_server[server] = 0 - by_server[server] += 1 +# Build a dynamic description from actual available backends +desc_parts = [] +for name, backend in backends().items(): + n = len(backend.tools()) + desc_parts.append("%s (%d tools)" % (name, n)) -desc_parts = ["%s (%d)" % (s, n) for s, n in by_server.items()] -summary = "Search for tools. Available servers: " + ", ".join(desc_parts) +summary = "Search for tools. Available: " + ", ".join(desc_parts) publish( - metadata(name="find_tool", description=summary, parameters=FIND_TOOL_SCHEMA), - lambda query: {"results": index.search(query)}, + metadata(name="find_tool", description=summary, + parameters=FIND_TOOL_SCHEMA, annotations={}), + lambda args: {"results": index.search(args["query"])}, ) publish( metadata(name="call_tool", description="Call a tool by name.", - parameters=CALL_TOOL_SCHEMA), - lambda tool_name, arguments: call_tool(tool_name, arguments), + parameters=CALL_TOOL_SCHEMA, annotations={}), + lambda args: call_tool(args["tool_name"], args["arguments"]), ) ``` @@ -241,24 +273,25 @@ When backends change and the session is recreated, the script re-runs and the de **Today's solution**: Not possible without writing a custom composite tool wrapper for every write tool. -**With programmable middleware**: +**With session initialization script**: ```python def with_approval_gate(fn, tool_name): - def wrapper(**args): + def wrapper(args): decision = elicit( "Tool '%s' may modify data. Approve?" % tool_name, schema={"type": "object", "properties": {"reason": {"type": "string"}}}, ) if decision.action != "accept": return {"error": "Declined by user"} - return fn(**args) + return fn(args) return wrapper -for meta, fn in tools(): - if not meta.annotations.get("readOnly", False): - fn = with_approval_gate(fn, meta.name) - publish(meta, fn) +for name, backend in backends().items(): + for meta, fn in backend.tools(): + if not meta.annotations.get("readOnly", False): + fn = with_approval_gate(fn, meta.name) + publish(meta, fn) ``` A single policy, applied once, covering all tools. @@ -269,19 +302,20 @@ A single policy, applied once, covering all tools. **Today's solution**: Requires a mutating webhook (THV-0017) calling an external service. -**With programmable middleware**: +**With session initialization script**: ```python def with_pii_scrubbing(fn): - def wrapper(**args): - result = fn(**args) + def wrapper(args): + result = fn(args) if "text" in result: result["text"] = scrub_pii(result["text"]) return result return wrapper -for meta, fn in tools(): - publish(meta, with_pii_scrubbing(fn)) +for name, backend in backends().items(): + for meta, fn in backend.tools(): + publish(meta, with_pii_scrubbing(fn)) ``` `scrub_pii()` is a Go-implemented built-in that applies regex-based and NER-based entity detection. It handles common patterns (emails, phone numbers, SSNs, credit cards) without requiring an external service. @@ -292,31 +326,33 @@ for meta, fn in tools(): **Today's solution**: `aggregation.tools[].overrides` for renaming, `aggregation.tools[].filter` / `excludeAll` for hiding. -**With programmable middleware**: +**With session initialization script**: ```python -for meta, fn in tools(): - # Hide internal tools - if meta.name.startswith("internal_"): - continue - - # Rename for clarity - if meta.name == "pg_query": - publish( - metadata(name="database_query", description="Query the production database", - parameters=meta.parameters, annotations=meta.annotations), - fn, - ) - continue - - # Skip Jira tools — we'll group them below - if meta.name in ["jira_create", "jira_update", "jira_search"]: - continue +for name, backend in backends().items(): + for meta, fn in backend.tools(): + # Hide internal tools + if meta.name.startswith("internal_"): + continue + + # Rename for clarity + if meta.name == "pg_query": + publish( + metadata(name="database_query", description="Query the production database", + parameters=meta.parameters, annotations=meta.annotations), + fn, + ) + continue + + # Skip Jira tools — we'll group them below + if meta.name in ["jira_create", "jira_update", "jira_search"]: + continue - publish(meta, fn) + publish(meta, fn) # Publish a composite Jira tool -def jira_handler(action, **args): +def jira_handler(args): + action = args["action"] if action == "create": return call_tool("jira_create", args) elif action == "update": @@ -326,7 +362,7 @@ def jira_handler(action, **args): publish( metadata(name="jira", description="Manage Jira issues: create, update, or search", - parameters=JIRA_SCHEMA), + parameters=JIRA_SCHEMA, annotations={}), jira_handler, ) ``` @@ -337,7 +373,7 @@ publish( **Today's solution**: THV-0057 provides static `requestsPerWindow` / `windowSeconds` per tool. -**With programmable middleware**: +**With session initialization script**: ```python LIMITS = { @@ -346,7 +382,7 @@ LIMITS = { } def with_rate_limit(fn, tool_name): - def wrapper(**args): + def wrapper(args): user = current_user() role = user.groups[0] if user.groups else "standard" role_limits = LIMITS.get(role, LIMITS["standard"]) @@ -357,11 +393,12 @@ def with_rate_limit(fn, tool_name): ) if not allowed: return {"error": "Rate limited", "retry_after": retry_after} - return fn(**args) + return fn(args) return wrapper -for meta, fn in tools(): - publish(meta, with_rate_limit(fn, meta.name)) +for name, backend in backends().items(): + for meta, fn in backend.tools(): + publish(meta, with_rate_limit(fn, meta.name)) ``` `check_rate_limit()` is backed by the same Redis token bucket from THV-0057. The *policy* is expressed in Starlark; the *mechanism* lives in Go. @@ -371,12 +408,16 @@ for meta, fn in tools(): A single script handles optimizer + elicitation gate + PII scrubbing + rate limiting — behaviors that today require four different config surfaces: ```python -all_tools = tools() +all_tools = [] +for name, backend in backends().items(): + all_tools += backend.tools() + index = search_index(all_tools) desc = build_summary(all_tools) -# Compose decorators for the call_tool dispatch path -def dispatch(tool_name, arguments): +def dispatch(args): + tool_name = args["tool_name"] + arguments = args["arguments"] user = current_user() # Rate limit @@ -400,13 +441,14 @@ def dispatch(tool_name, arguments): return result publish( - metadata(name="find_tool", description=desc, parameters=FIND_TOOL_SCHEMA), - lambda query: {"results": index.search(query)}, + metadata(name="find_tool", description=desc, + parameters=FIND_TOOL_SCHEMA, annotations={}), + lambda args: {"results": index.search(args["query"])}, ) publish( metadata(name="call_tool", description="Call a tool by name.", - parameters=CALL_TOOL_SCHEMA), + parameters=CALL_TOOL_SCHEMA, annotations={}), dispatch, ) @@ -428,13 +470,13 @@ The ordering is explicit. The interactions are visible. There are no surprising These are Go-implemented functions exposed to Starlark scripts. -#### Tool enumeration and publishing +#### Backend enumeration and publishing | Built-in | Signature | Description | |----------|-----------|-------------| -| `tools()` | `tools() → list[(metadata, handler)]` | Returns all authorized backend tools as `(metadata, handler)` tuples. `metadata` is a struct with `name`, `description`, `parameters`, `annotations`, `backend_id`. `handler` is a callable that invokes the backend. | -| `publish(meta, handler)` | `publish(metadata, callable) → None` | Adds a tool to the set visible to the agent. | -| `metadata(...)` | `metadata(name, description, parameters=None, annotations=None) → metadata` | Creates a new metadata struct. Used when renaming or defining new tools. | +| `backends()` | `backends() → dict[string, Backend]` | Returns all authorized backends keyed by name. Each `Backend` object exposes `.tools()` (returns `list[(metadata, handler)]`), `.resources()`, and `.prompts()`. Only backends and capabilities the current user is authorized to use are included. | +| `publish(meta, handler)` | `publish(metadata, callable) → None` | Adds a tool to the set visible to the agent. `handler` receives a single `dict` argument. | +| `metadata(...)` | `metadata(name, description, parameters, annotations) → metadata` | Creates a new metadata struct. All four fields are required — this prevents accidentally dropping `annotations` or `parameters` when renaming. | | `get_tool(name)` | `get_tool(name) → metadata or None` | Looks up a specific authorized tool's metadata by name. | #### Tool call execution @@ -446,7 +488,7 @@ These are Go-implemented functions exposed to Starlark scripts. | `retry(fn, max_attempts, delay)` | `retry(fn, max_attempts=3, delay="1s") → any` | Retries a callable with exponential backoff. | | `parallel(fns)` | `parallel(fns) → list` | Executes zero-argument callables concurrently. | -#### Middleware capabilities +#### Session initialization capabilities | Built-in | Signature | Description | |----------|-----------|-------------| @@ -461,90 +503,35 @@ These are Go-implemented functions exposed to Starlark scripts. The critical question is: how do people who don't want to write Starlark still use vMCP? -**Answer: presets.** A preset is a named, built-in Starlark script that replicates the behavior of today's config knobs. Existing config fields become parameters to the preset. - -Today's `Config` struct has top-level fields: `aggregation`, `compositeTools`, `compositeToolRefs`, and `optimizer`. The new `middleware` field sits alongside them: - -```go -type Config struct { - // ... existing fields unchanged ... - Aggregation *AggregationConfig `json:"aggregation,omitempty"` - CompositeTools []CompositeToolConfig `json:"compositeTools,omitempty"` - Optimizer *OptimizerConfig `json:"optimizer,omitempty"` - - // New field - Middleware *MiddlewareConfig `json:"middleware,omitempty"` -} -``` - -When `middleware` is set, it takes precedence over `aggregation`, `compositeTools`, and `optimizer`. When it is absent, those fields continue to work exactly as today — no behavior change for existing deployments. - -#### Config surface - -```yaml -# Option 1: Use a preset (maps to existing config patterns) -middleware: - preset: "standard" - -# Option 2: Preset with parameters (replacing today's config knobs) -middleware: - preset: "optimizer" - config: - optimizer: - embeddingService: "http://embedding-server:8080" - maxToolsToReturn: 8 - hybridSearchSemanticRatio: "0.5" - aggregation: - tools: - - workload: "backend-a" - excludeAll: true - - workload: "backend-b" - filter: ["search", "query"] - overrides: - search: - name: "global_search" - description: "Search across all sources" - piiScrubbing: - enabled: true - rateLimiting: - perUser: - requestsPerWindow: 100 - windowSeconds: 60 - -# Option 3: Custom script (power users) -middleware: - script: | - for meta, fn in tools(): - publish(meta, fn) +**Answer: presets.** A preset is a named, built-in Starlark script that replicates the behavior of today's config knobs. Presets are transparent — users can inspect the underlying Starlark source and fork it when they need customization: -# Option 4: External script file -middleware: - scriptFile: "middleware/policy.star" +```bash +thv vmcp show-preset optimizer ``` -The `config` block under a preset accepts the same structure as today's top-level config fields (`aggregation`, `optimizer`) plus new fields (`piiScrubbing`, `rateLimiting`). The preset script reads these via `config()` and translates them into the appropriate `publish()` calls and handler decorations. +This prints the Starlark source. A user who needs 90% of a preset's behavior can copy it, modify the 10% they need, and use `sessionInit.script` or `sessionInit.scriptFile` instead. #### Built-in presets | Preset | Behavior | Today's equivalent | |--------|----------|--------------------| | `passthrough` | Publishes all authorized tools unmodified. | No `aggregation`, no `optimizer` | -| `standard` | Applies filtering, renaming, conflict resolution, and optional rate limiting from `config`. | `aggregation` + `compositeTools` | -| `optimizer` | Publishes `find_tool` / `call_tool` with dynamic descriptions, applying filtering/renaming from `config`. Supports PII scrubbing and rate limiting. | `aggregation` + `optimizer` | +| `standard` | Applies filtering, renaming, and conflict resolution from the existing `aggregation` config. | `aggregation` config | +| `optimizer` | Publishes `find_tool` / `call_tool` with dynamic descriptions, applying filtering/renaming from `aggregation`. | `aggregation` + `optimizer` config | -Users can inspect what a preset does: +#### Migration path -```bash -thv vmcp show-preset optimizer -``` +The existing config fields (`aggregation`, `optimizer`, etc.) are **always** translated into a session initialization script internally. There is no separate legacy code path — the Starlark engine is the single implementation. -This prints the Starlark source, making the preset transparent and forkable. A user who needs 90% of a preset's behavior can copy it and modify the 10% they need. +When no `sessionInit` block is present, vMCP automatically generates the equivalent session initialization script from the existing config fields. This is the same script a user would get from running: -#### Migration path +```bash +thv vmcp migrate-config +``` -When no `middleware` block is present but `aggregation`, `compositeTools`, or `optimizer` fields exist, vMCP behaves exactly as today — the existing code paths run. No deprecation, no behavior change. +This command outputs the Starlark script equivalent of the current config, which the user can adopt as their `sessionInit.script` and customize from there. -When a user wants to adopt programmable middleware, they add a `middleware` block. At that point, `aggregation`, `compositeTools`, and `optimizer` are ignored (if both are present, vMCP logs a warning). A `thv vmcp migrate-config` command generates the equivalent `middleware` block from the existing config. +The only exception is legacy declarative composite tools (`compositeTools`, `compositeToolRefs`), which are not supported in the session initialization script. These are replaced by Starlark scripted tools from THV-0051. ### Detailed Design @@ -553,77 +540,62 @@ When a user wants to adopt programmable middleware, they add a `middleware` bloc ```mermaid sequenceDiagram participant Factory as Session Factory - participant Engine as Starlark Engine participant Cedar as Cedar Authz - participant Script as Middleware Script + participant Script as Session Init Script + participant Session as MultiSession participant Agent as MCP Client Note over Factory: Session creation - Factory->>Engine: Load script (preset or custom) - Engine->>Engine: Parse and validate - - Factory->>Cedar: Determine authorized tools for user - Cedar-->>Factory: Authorized tool set + Factory->>Factory: Load script (preset or generated from config) + Factory->>Cedar: Determine authorized backends for user + Cedar-->>Factory: Authorized backend set - Factory->>Engine: Execute script with authorized tools - Engine->>Script: Run top-level script body - Script->>Script: tools() → iterate, filter, wrap, publish - Script-->>Engine: Published (metadata, handler) set + Factory->>Script: Execute script with authorized backends + Script->>Script: backends() → iterate, filter, wrap, publish + Script-->>Factory: Published (metadata, handler) set + Factory->>Session: Construct MultiSession from published tools Note over Agent: tools/list - Agent->>Factory: tools/list - Factory-->>Agent: Published tool metadata + Agent->>Session: tools/list + Session-->>Agent: Published tool metadata Note over Agent: tools/call - Agent->>Factory: tools/call "find_tool" {query: "github"} - Factory->>Engine: Invoke published handler for "find_tool" - Engine->>Script: handler(query="github") - Script-->>Engine: {results: [...]} - Engine-->>Agent: CallToolResult + Agent->>Session: tools/call "find_tool" {query: "github"} + Session->>Session: Invoke published handler + Session-->>Agent: CallToolResult ``` -The script runs **once** per session, not per request. `publish()` calls build up the tool set. Handlers are stored and invoked later when the agent makes `tools/call` requests. +The script runs **once** per session, not per request. `publish()` calls build up the tool set. The resulting `(metadata, handler)` pairs are used to construct the `MultiSession`, which handles all subsequent `tools/list` and `tools/call` requests. #### Where this fits in the architecture -The Starlark middleware replaces the current decorator stack for tool-level concerns: +The session initialization script replaces the current decorator stack for tool-level concerns: ``` -Current decorator stack: New model: +Current model: New model: - optimizer decorator Starlark middleware - filter decorator (subsumes all of these) - composite tools decorator - base session base session + optimizer decorator Session factory runs + filter decorator session init script, + composite tools decorator constructs MultiSession + base session from publish() results ``` -The base session's routing table, conflict resolution, and backend name reversal (from RFC-0058) remain unchanged. The Starlark middleware sees post-resolution tool names. +The session initialization script is not a decorator — it is used during session construction. The session factory runs the script, collects `publish()` calls, and uses the resulting `(metadata, handler)` pairs to build the `MultiSession`. The `MultiSession` is the same construct already wired into the server — it handles `Tools()` and `CallTool()` using the published tools and handlers. -Concretely, the Starlark engine is a single session decorator that: -- Runs the script at session creation, collecting `publish()` calls -- Returns published tool metadata for `Tools()` calls -- Dispatches `CallTool()` to the published handler for the requested tool +#### Interaction with authorization -#### Interaction with Cedar authorization +The session initialization script runs after authorization has determined which backends and tools are available. `backends()` returns only what the user is authorized to use. `call_tool()` delegates to the base session, which enforces the routing table. The script cannot escalate privileges. -Cedar policies operate at the HTTP middleware layer. The Starlark engine receives only authorized tools: - -1. Authentication middleware extracts user identity -2. Cedar middleware evaluates policies, determines authorized tools -3. Session layer receives authorized tool set -4. `tools()` returns only tools that passed Cedar -5. `call_tool()` delegates to the base session, which enforces the routing table - -If a script attempts `call_tool("secret_admin_tool", ...)` for an unauthorized user, the base session rejects it. The script cannot escalate privileges. +The specific mechanism for authorization (Cedar policies at the HTTP middleware layer, or a future alternative) is orthogonal to this design. Additional built-in functions could be added in the future to make the authorization integration more explicit within the script, but that is out of scope for this RFC. #### Interaction with dynamic webhooks -Webhooks (THV-0017) and Starlark middleware serve different purposes at different layers: +Webhooks (THV-0017) and Starlark session initialization serve different purposes at different layers: - **Webhooks** integrate **external systems** at the HTTP middleware layer - **Starlark** configures **vMCP-internal behavior** at the session layer -Both coexist. A request passes through webhooks first (external policy), then reaches the Starlark-published handler (internal routing). +Both coexist. A request passes through webhooks first (external policy), then reaches the published handler (internal routing). Additional built-in functions could be added in the future to make the webhook integration more explicit within the script, but that is out of scope for this RFC. #### Interaction with rate limiting @@ -637,47 +609,40 @@ THV-0057's Redis-backed token bucket is the *mechanism*. `check_rate_limit()` ex #### New config fields ```go -type MiddlewareConfig struct { - // Preset is a named built-in middleware script. +type SessionInitConfig struct { + // Preset is a named built-in session initialization script. // One of: "passthrough", "standard", "optimizer". + // When empty and no Script/ScriptFile is set, the session init script + // is auto-generated from the existing aggregation/optimizer config. Preset string `json:"preset,omitempty" yaml:"preset,omitempty"` - // Config is passed to the preset script via config(). - // Accepts the same structure as today's aggregation, optimizer, etc. - Config *MiddlewarePresetConfig `json:"config,omitempty" yaml:"config,omitempty"` - // Script is inline Starlark source. Mutually exclusive with Preset and ScriptFile. Script string `json:"script,omitempty" yaml:"script,omitempty"` // ScriptFile is a path to a .star file. Mutually exclusive with Preset and Script. ScriptFile string `json:"scriptFile,omitempty" yaml:"scriptFile,omitempty"` } - -type MiddlewarePresetConfig struct { - Aggregation *AggregationConfig `json:"aggregation,omitempty"` - Optimizer *OptimizerConfig `json:"optimizer,omitempty"` - PIIScrubbing *PIIScrubConfig `json:"piiScrubbing,omitempty"` - RateLimiting *RateLimitConfig `json:"rateLimiting,omitempty"` -} ``` -#### Existing config fields: no change +#### Existing config fields + +`aggregation`, `compositeToolRefs`, and `optimizer` remain on `Config`. When no `sessionInit` block is present, they are used to auto-generate the session initialization script. When `sessionInit` is present, `aggregation` and `optimizer` are ignored (if both are set, vMCP logs a warning). -`aggregation`, `compositeTools`, `compositeToolRefs`, and `optimizer` remain on `Config` and work exactly as today when `middleware` is absent. When `middleware` is present, they are ignored (with a warning if both exist). +Legacy declarative composite tools (`compositeTools`, `compositeToolRefs`) are not supported in the session initialization script and will be removed in a future release. #### New CRD -`VirtualMCPMiddlewareScript` — references a Starlark middleware script from a ConfigMap: +`VirtualMCPSessionInitScript` — references a Starlark session initialization script from a ConfigMap: ```yaml apiVersion: toolhive.stacklok.com/v1alpha1 -kind: VirtualMCPMiddlewareScript +kind: VirtualMCPSessionInitScript metadata: - name: my-org-middleware + name: my-org-session-init spec: configMapRef: - name: vmcp-middleware-scripts - key: policy.star + name: vmcp-session-init-scripts + key: init.star ``` ## Security Considerations @@ -696,11 +661,11 @@ spec: **Cedar remains the authorization boundary.** The Starlark engine cannot circumvent it: -- `tools()` returns only Cedar-authorized tools -- `call_tool()` delegates to the base session's `CallTool()`, which checks the routing table built from Cedar-authorized tools only -- `publish()` can publish tools from `tools()` or new tools whose handlers use `call_tool()` — which is Cedar-gated +- `backends()` returns only authorized backends and tools +- `call_tool()` delegates to the base session's `CallTool()`, which checks the routing table built from authorized tools only +- `publish()` can publish tools from `backends()` or new tools whose handlers use `call_tool()` — which is authorization-gated -**Trust model**: Middleware scripts are written by administrators, not end users. An administrator who can write a Starlark script already has the authority to configure vMCP. +**Trust model**: Session initialization scripts are written by administrators, not end users. An administrator who can write a Starlark script already has the authority to configure vMCP. ### Data Security @@ -766,9 +731,9 @@ Scripts have no access to secrets. Backend authentication is handled below the s ### Backward Compatibility -All existing config fields continue to work unchanged. `middleware` is a new, optional field. When absent, existing code paths run. No deprecation of existing fields in this RFC — they remain first-class until the ecosystem has adopted programmable middleware. +All existing config fields (`aggregation`, `optimizer`) continue to produce identical behavior. Internally, they are translated into a session initialization script rather than running through a separate legacy code path. Users can run `thv vmcp migrate-config` to see and adopt the generated script. -When `middleware` is present, `aggregation`, `compositeTools`, and `optimizer` are ignored. If both are set, vMCP logs a warning. +The exception is legacy declarative composite tools (`compositeTools`, `compositeToolRefs`), which are replaced by Starlark scripted tools from THV-0051. ### Forward Compatibility @@ -776,74 +741,64 @@ New built-in functions can be added without breaking existing scripts. New prese ## Implementation Plan -### Phase 1: Core engine — `tools()`, `publish()`, `metadata()` +### Phase 1: Feature parity — session initialization replaces existing decorators -- Extend the Starlark engine from THV-0051 with `tools()`, `publish()`, `metadata()`, `get_tool()` built-ins -- Implement the session decorator that collects `publish()` calls and dispatches `CallTool()` to handlers -- Implement `passthrough` preset -- Config model: `middleware.preset`, `middleware.script`, `middleware.scriptFile` -- Unit tests, integration test for tool publishing and handler dispatch - -### Phase 2: Built-in capabilities and presets +The first deliverable must produce identical behavior to the existing config-driven system (except for legacy composite tools). This is the critical migration gate. +- Extend the Starlark engine from THV-0051 with `backends()`, `publish()`, `metadata()`, `get_tool()` built-ins +- Session factory runs the script and constructs `MultiSession` from `publish()` results - Port `search_index()` from current optimizer implementation -- Implement `scrub_pii()` built-in -- Implement `check_rate_limit()` built-in (backed by THV-0057's Redis token bucket) -- Implement `standard` and `optimizer` presets with `config` parameter support -- Integration tests for each built-in, E2E tests for presets in K8s - -### Phase 3: Migration tooling +- Implement `passthrough`, `standard`, and `optimizer` presets +- Auto-generate session init script from existing `aggregation` / `optimizer` config when no `sessionInit` block is present +- `thv vmcp migrate-config` command to output the generated script +- `thv vmcp show-preset` command to inspect built-in presets +- Config model: `sessionInit.preset`, `sessionInit.script`, `sessionInit.scriptFile` +- Preset equivalence tests: verify every preset produces identical behavior to the old config-driven feature it replaces +- Remove optimizer, filter, and composite tools decorators — the session init script is the single implementation -- Implement `thv vmcp migrate-config` CLI command -- Implement `thv vmcp show-preset` command -- Port existing tests to validate preset equivalence with old config paths -- Documentation +### Phase 2: New capabilities -### Phase 4: Cleanup (future) +These are net-new built-in functions that make new use cases *possible*. The scope of this phase is to add the built-in functions, not to ship fully-featured implementations. -- Remove optimizer, filter, and composite tools decorators -- Consolidated test suite +- Implement `scrub_pii()` built-in +- Implement `check_rate_limit()` built-in (backed by THV-0057's Redis token bucket when available) +- E2E tests for custom scripts in K8s via ConfigMap +- Documentation: user guide, built-in reference, migration guide ### Dependencies - THV-0051 (Starlark engine core) — base engine, value converter, `call_tool`, `try_call_tool`, `retry`, `parallel`, `elicit`, `log` -- THV-0058 (aggregator decomposition) — clean base session for the decorator to sit on -- THV-0057 (rate limiting) — Redis token bucket for `check_rate_limit()` +- THV-0057 (rate limiting) — Redis token bucket for `check_rate_limit()` (Phase 2 only) ## Testing Strategy - **Unit tests**: Each built-in in isolation. Handler wrapping / function composition. `publish()` validation. Preset loading and config injection. - **Integration tests**: Full script execution with mock backends. Decorator chains. Composite tool handlers via `call_tool()`. Optimizer pattern with `search_index()`. -- **E2E tests**: Preset configuration in K8s. Custom scripts via ConfigMap. Old config → middleware migration. +- **E2E tests**: Preset configuration in K8s. Custom scripts via ConfigMap. Old config → session init migration. - **Security tests**: `tools()` respects Cedar. `call_tool()` rejects unauthorized tools. Step limits. Memory. - **Preset equivalence tests**: For each preset, verify behavior matches the old config-driven feature it replaces. ## Documentation -- **User guide**: Writing middleware scripts, built-in reference, decorator patterns -- **Preset reference**: What each preset does, parameters, `show-preset` and forking -- **Migration guide**: From old config knobs to middleware presets or custom scripts -- **Architecture docs**: Updated vMCP architecture with middleware model -- **CRD reference**: `VirtualMCPMiddlewareScript` +- **User guide**: Writing session initialization scripts, built-in reference, decorator patterns +- **Preset reference**: What each preset does, `show-preset` and forking +- **Migration guide**: From old config knobs to session init presets or custom scripts +- **Architecture docs**: Updated vMCP architecture with session initialization model +- **CRD reference**: `VirtualMCPSessionInitScript` ## Open Questions -1. **Handler argument passing**: Should handlers receive keyword arguments (`fn(**args)`) or a single dict (`fn(args)`)? Keyword args are more Pythonic but Starlark's `**kwargs` support varies. A single dict matches `call_tool(name, args)` and is simpler. - -2. **Should presets be composable?** Could a user layer multiple presets, or is a single preset + config sufficient? Multiple presets add complexity in ordering and config conflicts. - -3. **Hot reloading**: Should ConfigMap updates to scripts trigger live session recreation? Convenient but complex (re-validation, in-flight calls). +1. **Should presets be composable?** Could a user layer multiple presets, or is a single preset sufficient? Multiple presets add complexity in ordering and config conflicts. -4. **Interaction with existing `compositeTools` during migration**: Should middleware scripts be able to `load()` composite tool definitions from CRDs, or must everything be consolidated into the script? +2. **Hot reloading**: Should ConfigMap updates to scripts trigger live session recreation? Convenient but complex (re-validation, in-flight calls). -5. **Custom PII patterns**: Should `scrub_pii()` accept custom regex patterns (e.g., internal employee ID formats), or is the built-in set sufficient? +3. **Custom PII patterns**: Should `scrub_pii()` accept custom regex patterns (e.g., internal employee ID formats), or is the built-in set sufficient? -6. **Thread safety of `parallel()`**: When `parallel()` invokes handlers that themselves call `call_tool()`, each goroutine needs its own Starlark thread. The engine must ensure published handlers are safe to call concurrently. +4. **Error handling in handler functions**: When a handler function calls `call_tool()` and it fails, the script halts and the agent receives an error. How should administrators configure error handling behavior? Options include: (a) `try_call_tool()` for opt-in error handling (already in THV-0051), (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). ## References - [THV-0051: Starlark Scripted Tools](./THV-0051-starlark-scripted-tools.md) — original Starlark RFC -- [THV-0058: Inline Aggregator, Extract Filter Decorator](./THV-0058-inline-aggregator-filter-decorator.md) — aggregator decomposition - [THV-0057: Rate Limiting](./THV-0057-rate-limiting.md) — rate limiting mechanism - [THV-0017: Dynamic Webhook Middleware](./THV-0017-dynamic-webhook-middleware.md) — external webhook integration - [stacklok-epics#213](https://github.com/stacklok/stacklok-epics/issues/213) — Dynamic Webhook Middleware epic From 7ae3cffbc21315879484fb8fa3cb7503da3ee31c Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 25 Mar 2026 17:55:59 -0700 Subject: [PATCH 03/19] Overhaul RFC-0059 based on review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite authz model: scripts see all backends, authz filters at runtime - Add Background section explaining how authorization works today - Replace call_tool() with saved handler dict pattern in all examples - Collapse three presets into single `default` preset with ~80-line sketch - Add handler timeout support (optional kwarg) - Move current_user() to future built-ins (user known at init, deferred) - Fix scrub_pii() as future built-in example, not v0 deliverable - Add config() built-in for preset access to persona config - Expand Alternatives Considered: configuration approaches + language considerations (Starlark, Risor, Wasm, Lua, OPA/Rego) - Add 4-phase implementation plan (POC → production → deprecate → ship) - Update Why Now with cost equation argument - Clarify scope: enabling new capabilities, not shipping them Co-Authored-By: Claude Opus 4.6 --- ...V-0059-starlark-programmable-middleware.md | 467 ++++++++++++------ 1 file changed, 329 insertions(+), 138 deletions(-) diff --git a/rfcs/THV-0059-starlark-programmable-middleware.md b/rfcs/THV-0059-starlark-programmable-middleware.md index bca3e20..a5764cb 100644 --- a/rfcs/THV-0059-starlark-programmable-middleware.md +++ b/rfcs/THV-0059-starlark-programmable-middleware.md @@ -10,7 +10,7 @@ ## Summary -Introduce a Starlark-based session initialization script for vMCP. A single script runs once per session, receives the authorized backends and their capabilities, and calls `publish()` to declare what the agent sees — optionally wrapping handlers with additional logic. This replaces the growing set of independent config knobs (aggregation, optimizer, filtering, rate limiting) whose combinations interact in ways that are difficult to predict, test, and explain. +Introduce a Starlark-based session initialization script for vMCP. A single script runs once per session, receives discovered backends and their capabilities, and calls `publish()` to declare what the agent sees — optionally wrapping handlers with additional logic. Existing config knobs remain fully supported, but customization of vMCP behavior can now be exactly tailored to the use case without adding more knobs and the cognitive load of their interactions. ## Problem Statement @@ -54,11 +54,30 @@ A config knob for "optimizer description template" would fix this one case. But THV-0051 proposes Starlark for composite tools. Before that engine ships, we should decide whether Starlark is *only* for composite tools or whether it's the foundation for a unified session initialization model. Shipping THV-0051 as-is and then later expanding scope would mean a second migration. +The cost equation also favors acting now. As more capabilities are added, the cost of retrofitting a composable system increases — more code to replace, more interactions to preserve. Meanwhile, the cost of building each new config knob is *also* increasing, because each knob must reason about its interactions with every existing knob and implement more than a simple built-in. A session initialization model inverts this: new capabilities ship as simple built-in functions, and administrators compose them as needed. The longer we wait, the more expensive both paths become. + +### Background: How authorization works today + +Authorization in vMCP is enforced by authz middleware that sits between the client and the session: + +- For **`tools/list`** (and other list methods): the authz middleware filters the response, removing items the caller isn't authorized to use. +- For **`tools/call`** (and other action methods): the authz middleware gates the request, returning 403 if the caller isn't authorized. + +Critically, the authz middleware operates on the *final published tool set* — it does not restrict which tools are visible during session construction. The session initialization script runs during session construction, so it sees all tools from all backends the persona is configured to use. Authorization is applied afterward at request time. + +**Implication for this RFC**: Because the script sees all tools regardless of the caller's authorization: + +1. `backends()` returns all backends configured for the persona, not filtered by user authorization. +2. Published tools are still subject to authz filtering/gating at runtime — publishing a tool does not bypass authorization. +3. A handler could dispatch to any discovered tool via saved references, including tools the current user isn't authorized to use. This is the same trust model as composite tools today — see [Interaction with authorization](#interaction-with-authorization). + +Fixing the authz boundary is out of scope for this RFC. + ## Goals - Define a Starlark-based programming model that subsumes tool advertising, renaming, optimizer behavior, and composite tool workflows into a single script that runs once per session -- Provide built-in functions for capabilities that would otherwise be config knobs: search indexing, PII scrubbing, rate limiting checks -- Maintain the invariant that **Starlark never sees tools the user is not authorized to use** — Cedar authorization remains the access control boundary +- Make it easy to add new capabilities (search indexing, PII scrubbing, rate limiting) as simple built-in functions rather than config knobs with complex interactions +- Preserve the existing authorization boundary — Cedar authz middleware continues to filter `tools/list` and gate `tools/call` at runtime, independent of what the script publishes - Make the system accessible to non-power-users via built-in presets that replicate today's config-driven behavior - Enable policies that span multiple features (e.g., "non-readonly tools require elicitation") - Maintain full backward compatibility with existing config fields — the session initialization script must be able to replicate every behavior currently achievable via `aggregation`, `optimizer`, and related config (except legacy composite tools, which are replaced by Starlark scripts from THV-0051) @@ -75,12 +94,13 @@ THV-0051 proposes Starlark for composite tools. Before that engine ships, we sho ### High-Level Design -A vMCP persona runs a single Starlark **session initialization script** once per session. The script receives the authorized backends via `backends()` — a dict keyed by backend name, where each value exposes the backend's tools, resources, and prompts. The script calls `publish()` to declare what the agent sees. +A vMCP persona runs a single Starlark **session initialization script** once per session. The script receives discovered backends via `backends()` — a dict keyed by backend name, where each value exposes the backend's tools, resources, and prompts. The script calls `publish()` to declare what the agent sees. ```mermaid flowchart TB subgraph "Session Initialization" - Cedar[Cedar Authorization] -->|authorized backends| Factory[Session Factory] + Factory[Session Factory] -->|discover backends| Backends[Backend MCP Servers] + Backends -->|capabilities| Factory Factory -->|run script once| Script[Session Init Script] Script -->|"publish(meta, fn)"| Session[MultiSession] end @@ -89,31 +109,32 @@ flowchart TB backends_fn["backends() → dict of backend objects"] publish_fn["publish(metadata, handler)"] search_fn["search_index(tools) → index"] - scrub_fn["scrub_pii(text) → text"] - rate_fn["check_rate_limit(key, limit, window) → (ok, retry_after)"] + config_fn["config() → persona config dict"] elicit_fn["elicit(message, schema) → decision"] - call_fn["call_tool(name, args) → result"] end - subgraph "Runtime" - Agent[MCP Client] -->|tools/list| Session - Agent -->|tools/call| Handler[Published Handler] - Handler -->|backend tools| Backend[Backend MCP Servers] + subgraph "Runtime (per request)" + Agent[MCP Client] -->|tools/list| Authz[Authz Middleware] + Agent -->|tools/call| Authz + Authz -->|filtered/gated| Session + Session -->|invoke handler| Backend2[Backend MCP Servers] end style Factory fill:#90caf9 - style Cedar fill:#ffcc80 + style Authz fill:#ffcc80 ``` -**Key invariant**: `backends()` returns only backends and capabilities the current user is authorized to use. Cedar policies are evaluated *before* the script runs. The script operates within the authorization boundary, not outside it. +**Key invariant**: `backends()` returns all backends configured for the persona. The script publishes tools, and the authz middleware filters `tools/list` responses and gates `tools/call` requests at runtime. Publishing a tool does not bypass authorization. ### The Programming Model The script runs once when a session is created. `backends()` returns a dict keyed by backend name. Each backend object exposes: - **`backend.tools()`** — returns a list of `(metadata, handler)` tuples for the backend's tools -- **`backend.resources()`** — returns the backend's resources (future use) -- **`backend.prompts()`** — returns the backend's prompts (future use) +- **`backend.resources()`** — returns the backend's resources +- **`backend.prompts()`** — returns the backend's prompts + +**Resources and prompts in v0**: Today, vMCP aggregates resources and prompts from all backends and passes them through unmodified — no conflict resolution, renaming, or filtering is applied (unlike tools). In v0, the session initialization script only controls tools via `publish()`. Resources and prompts continue to be passed through from backends unchanged. Future versions may add `publish_resource()` and `publish_prompt()` to give scripts control over these capabilities as well. Each tool tuple is `(metadata, handler)`: @@ -125,7 +146,7 @@ Each tool tuple is `(metadata, handler)`: #### Simplest possible script ```python -# Publish everything the user is authorized to use. No modification. +# Publish everything from all backends. No modification. for name, backend in backends().items(): for meta, fn in backend.tools(): publish(meta, fn) @@ -229,6 +250,8 @@ publish( ### Motivating Use Cases +The following use cases illustrate what the programming model enables. Use Cases 1, 2, and 4 are achievable with the v0 built-ins. Use Cases 3, 5, and 6 depend on future built-ins (`scrub_pii()`, `check_rate_limit()`) that are not in scope for this RFC but demonstrate why the model is worth building. + #### Use Case 1: Dynamic optimizer descriptions **Problem**: Agents don't use `find_tool` because its static description doesn't tell them what's available. @@ -258,14 +281,20 @@ publish( lambda args: {"results": index.search(args["query"])}, ) +# Save handlers by name for dispatch +tool_handlers = {} +for name, backend in backends().items(): + for meta, fn in backend.tools(): + tool_handlers[meta.name] = fn + publish( metadata(name="call_tool", description="Call a tool by name.", parameters=CALL_TOOL_SCHEMA, annotations={}), - lambda args: call_tool(args["tool_name"], args["arguments"]), + lambda args: tool_handlers[args["tool_name"]](args["arguments"]), ) ``` -When backends change and the session is recreated, the script re-runs and the description updates. A `tools/list_changed` notification is sent to clients that support it. +The value here is that the administrator defines the policy that works best for their use case. ToolHive doesn't need to build one-size-fits-all solutions for optimizer behavior — the script is the policy. #### Use Case 2: Elicitation gate for write operations @@ -318,7 +347,7 @@ for name, backend in backends().items(): publish(meta, with_pii_scrubbing(fn)) ``` -`scrub_pii()` is a Go-implemented built-in that applies regex-based and NER-based entity detection. It handles common patterns (emails, phone numbers, SSNs, credit cards) without requiring an external service. +`scrub_pii()` is an example of a future built-in (not in scope for this RFC) that could handle common patterns (emails, phone numbers, SSNs, credit cards). Users can also write their own scrubbing decorators — the programming model makes this natural without needing ToolHive to explicitly support every scrubbing pattern. #### Use Case 4: Tool aggregation and renaming @@ -329,6 +358,8 @@ for name, backend in backends().items(): **With session initialization script**: ```python +jira_handlers = {} + for name, backend in backends().items(): for meta, fn in backend.tools(): # Hide internal tools @@ -344,21 +375,17 @@ for name, backend in backends().items(): ) continue - # Skip Jira tools — we'll group them below + # Save Jira tools — we'll group them below if meta.name in ["jira_create", "jira_update", "jira_search"]: + jira_handlers[meta.name] = fn continue publish(meta, fn) -# Publish a composite Jira tool +# Publish a composite Jira tool using saved handlers def jira_handler(args): action = args["action"] - if action == "create": - return call_tool("jira_create", args) - elif action == "update": - return call_tool("jira_update", args) - elif action == "search": - return call_tool("jira_search", args) + return jira_handlers["jira_" + action](args) publish( metadata(name="jira", description="Manage Jira issues: create, update, or search", @@ -401,16 +428,33 @@ for name, backend in backends().items(): publish(meta, with_rate_limit(fn, meta.name)) ``` -`check_rate_limit()` is backed by the same Redis token bucket from THV-0057. The *policy* is expressed in Starlark; the *mechanism* lives in Go. +`check_rate_limit()` is backed by the same Redis token bucket from THV-0057. The *policy* is expressed in Starlark; the *mechanism* lives in Go. This allows users to define rate limiting that meets the needs of their use case without ToolHive needing to explicitly support every rate limiting pattern. #### Use Case 6: Composing multiple concerns A single script handles optimizer + elicitation gate + PII scrubbing + rate limiting — behaviors that today require four different config surfaces: ```python +def build_summary(tool_list): + cats = {} + for meta, fn in tool_list: + cat = meta.annotations.get("category", "general") + if cat not in cats: + cats[cat] = [] + cats[cat].append(meta.name) + return "Search for tools across: " + ", ".join( + "%s (%d tools)" % (c, len(ns)) for c, ns in cats.items() + ) + all_tools = [] +tool_handlers = {} +tool_metadata = {} + for name, backend in backends().items(): - all_tools += backend.tools() + for meta, fn in backend.tools(): + all_tools.append((meta, fn)) + tool_handlers[meta.name] = fn + tool_metadata[meta.name] = meta index = search_index(all_tools) desc = build_summary(all_tools) @@ -428,14 +472,14 @@ def dispatch(args): return {"error": "Rate limited", "retry_after": retry_after} # Elicitation gate for non-readonly tools - t = get_tool(tool_name) - if t and not t.annotations.get("readOnly", False): + meta = tool_metadata.get(tool_name) + if meta and not meta.annotations.get("readOnly", False): decision = elicit("Approve call to '%s'?" % tool_name) if decision.action != "accept": return {"error": "Declined"} # Execute and scrub - result = call_tool(tool_name, arguments) + result = tool_handlers[tool_name](arguments) if "text" in result: result["text"] = scrub_pii(result["text"]) return result @@ -451,17 +495,6 @@ publish( parameters=CALL_TOOL_SCHEMA, annotations={}), dispatch, ) - -def build_summary(tool_list): - cats = {} - for meta, fn in tool_list: - cat = meta.annotations.get("category", "general") - if cat not in cats: - cats[cat] = [] - cats[cat].append(meta.name) - return "Search for tools across: " + ", ".join( - "%s (%d tools)" % (c, len(ns)) for c, ns in cats.items() - ) ``` The ordering is explicit. The interactions are visible. There are no surprising feature interactions because the administrator wrote the interaction. @@ -474,30 +507,42 @@ These are Go-implemented functions exposed to Starlark scripts. | Built-in | Signature | Description | |----------|-----------|-------------| -| `backends()` | `backends() → dict[string, Backend]` | Returns all authorized backends keyed by name. Each `Backend` object exposes `.tools()` (returns `list[(metadata, handler)]`), `.resources()`, and `.prompts()`. Only backends and capabilities the current user is authorized to use are included. | -| `publish(meta, handler)` | `publish(metadata, callable) → None` | Adds a tool to the set visible to the agent. `handler` receives a single `dict` argument. | +| `backends()` | `backends() → dict[string, Backend]` | Returns all backends configured for the persona, keyed by name. Each `Backend` object exposes `.tools()` (returns `list[(metadata, handler)]`), `.resources()`, and `.prompts()`. | +| `publish(meta, handler)` | `publish(metadata, callable) → None` | Adds a tool to the set visible to the agent. `handler` receives a single `dict` argument. Authz middleware still filters/gates at runtime. | | `metadata(...)` | `metadata(name, description, parameters, annotations) → metadata` | Creates a new metadata struct. All four fields are required — this prevents accidentally dropping `annotations` or `parameters` when renaming. | -| `get_tool(name)` | `get_tool(name) → metadata or None` | Looks up a specific authorized tool's metadata by name. | -#### Tool call execution +#### Session initialization capabilities | Built-in | Signature | Description | |----------|-----------|-------------| -| `call_tool(name, args)` | `call_tool(name, dict) → dict` | Calls a backend tool by name. Halts on error. | -| `try_call_tool(name, args)` | `try_call_tool(name, dict) → struct(ok, error, output)` | Calls a backend tool. Returns error info instead of halting. | -| `retry(fn, max_attempts, delay)` | `retry(fn, max_attempts=3, delay="1s") → any` | Retries a callable with exponential backoff. | -| `parallel(fns)` | `parallel(fns) → list` | Executes zero-argument callables concurrently. | +| `search_index(tools)` | `search_index(list[(metadata, handler)]) → SearchIndex` | Builds a semantic search index over the tool list. Returns an object with `.search(query) → list[dict]`. | +| `elicit(message, schema)` | `elicit(message, schema={}) → struct(action, content)` | Prompts the user for a decision via MCP elicitation. | +| `config()` | `config() → dict` | Returns the vMCP persona's config fields (`aggregation`, `optimizer`, etc.) as a read-only dict. Used by the `default` preset. | +| `log(message)` | `log(message) → None` | Emits a structured audit log entry. | -#### Session initialization capabilities +#### Handler options + +Handlers returned from `backend.tools()` accept an optional `timeout` keyword argument to control backend call timeouts: + +```python +# Default timeout (inherited from backend config) +result = fn(args) + +# Custom timeout +result = fn(args, timeout=10) # 10 second timeout +``` + +This is useful for handlers that wrap expensive backend calls or for scripts that need tighter latency guarantees. + +#### Future built-ins (examples) + +The programming model makes it straightforward to add new capabilities as simple built-in functions. These are examples of what could be added — they are **not** in scope for this RFC: | Built-in | Signature | Description | |----------|-----------|-------------| -| `search_index(tools)` | `search_index(list[(metadata, handler)]) → SearchIndex` | Builds a semantic search index over the tool list. Returns an object with `.search(query) → list[dict]`. | +| `current_user()` | `current_user() → struct(sub, email, groups)` | Returns the authenticated user's identity. The user is known at init time, but this built-in is deferred to a future version. | | `scrub_pii(text)` | `scrub_pii(text) → string` | Redacts PII patterns (emails, phones, SSNs, credit cards) from text. | | `check_rate_limit(key, limit, window)` | `check_rate_limit(key, limit, window) → (bool, int)` | Checks a token bucket counter in Redis. Returns `(allowed, retry_after_seconds)`. | -| `elicit(message, schema)` | `elicit(message, schema={}) → struct(action, content)` | Prompts the user for a decision via MCP elicitation. | -| `current_user()` | `current_user() → struct(sub, email, groups)` | Returns the authenticated user's identity. | -| `log(message)` | `log(message) → None` | Emits a structured audit log entry. | ### Presets: Making it Easy for Non-Power-Users @@ -506,7 +551,7 @@ The critical question is: how do people who don't want to write Starlark still u **Answer: presets.** A preset is a named, built-in Starlark script that replicates the behavior of today's config knobs. Presets are transparent — users can inspect the underlying Starlark source and fork it when they need customization: ```bash -thv vmcp show-preset optimizer +thv vmcp show-preset default ``` This prints the Starlark source. A user who needs 90% of a preset's behavior can copy it, modify the 10% they need, and use `sessionInit.script` or `sessionInit.scriptFile` instead. @@ -515,23 +560,113 @@ This prints the Starlark source. A user who needs 90% of a preset's behavior can | Preset | Behavior | Today's equivalent | |--------|----------|--------------------| -| `passthrough` | Publishes all authorized tools unmodified. | No `aggregation`, no `optimizer` | -| `standard` | Applies filtering, renaming, and conflict resolution from the existing `aggregation` config. | `aggregation` config | -| `optimizer` | Publishes `find_tool` / `call_tool` with dynamic descriptions, applying filtering/renaming from `aggregation`. | `aggregation` + `optimizer` config | +| `default` | Reads existing config knobs (`aggregation`, `optimizer`, etc.) and produces identical behavior to the current config-driven system. Applies filtering, renaming, conflict resolution, and optimizer behavior based on what's configured. | All existing config | -#### Migration path +A single `default` preset handles all existing config knobs. When no `sessionInit` block is present, vMCP uses the `default` preset, which reads the existing config fields and produces identical behavior. There is no separate legacy code path — the Starlark engine is the single implementation. -The existing config fields (`aggregation`, `optimizer`, etc.) are **always** translated into a session initialization script internally. There is no separate legacy code path — the Starlark engine is the single implementation. +The only exception is legacy declarative composite tools (`compositeTools`, `compositeToolRefs`), which are not supported in the session initialization script. These are replaced by Starlark scripted tools from THV-0051. -When no `sessionInit` block is present, vMCP automatically generates the equivalent session initialization script from the existing config fields. This is the same script a user would get from running: +#### Sketch of the `default` preset -```bash -thv vmcp migrate-config -``` +The `default` preset is the most complex part of this RFC — it must faithfully replicate the behavior of the existing config-driven system. Below is a sketch of what this script looks like. The exact implementation will be validated during the POC phase. -This command outputs the Starlark script equivalent of the current config, which the user can adopt as their `sessionInit.script` and customize from there. +```python +cfg = config() +agg = cfg.get("aggregation", {}) +opt = cfg.get("optimizer", None) -The only exception is legacy declarative composite tools (`compositeTools`, `compositeToolRefs`), which are not supported in the session initialization script. These are replaced by Starlark scripted tools from THV-0051. +# --- Conflict resolution strategy --- +strategy = agg.get("conflictResolution", "prefix") +prefix_format = "{workload}_" +priority_order = [] +cr_config = agg.get("conflictResolutionConfig", {}) +if cr_config: + prefix_format = cr_config.get("prefixFormat", "{workload}_") + priority_order = cr_config.get("priorityOrder", []) + +# --- Collect tools per backend, applying filtering and overrides --- +tool_configs = {} +for tc in agg.get("tools", []): + tool_configs[tc["workload"]] = tc + +all_published = [] # track names for conflict detection +seen_names = {} # name → backend for collision detection + +for backend_name, backend in backends().items(): + tc = tool_configs.get(backend_name, {}) + + # ExcludeAll: skip entire backend + if agg.get("excludeAllTools", False) or tc.get("excludeAll", False): + continue + + allowed = tc.get("filter", None) # None = allow all + overrides = tc.get("overrides", {}) + + for meta, fn in backend.tools(): + # Apply filter (allow-list) + if allowed and meta.name not in allowed: + continue + + # Apply overrides (renaming, description changes) + override = overrides.get(meta.name, None) + if override: + meta = metadata( + name = override.get("name", meta.name), + description = override.get("description", meta.description), + parameters = meta.parameters, + annotations = meta.annotations, + ) + + # Apply conflict resolution + if meta.name in seen_names: + if strategy == "prefix": + prefix = prefix_format.replace("{workload}", backend_name) + meta = metadata( + name = prefix + meta.name, + description = meta.description, + parameters = meta.parameters, + annotations = meta.annotations, + ) + elif strategy == "priority": + existing_backend = seen_names[meta.name] + if priority_order.index(backend_name) > priority_order.index(existing_backend): + continue # lower priority, skip + # else: higher priority, will overwrite + + seen_names[meta.name] = backend_name + all_published.append((meta, fn)) + +# --- Optimizer mode: publish find_tool/call_tool instead of raw tools --- +if opt: + index = search_index(all_published) + + desc_parts = [] + for backend_name, backend in backends().items(): + n = len(backend.tools()) + desc_parts.append("%s (%d tools)" % (backend_name, n)) + summary = "Search for tools. Available: " + ", ".join(desc_parts) + + tool_handlers = {} + for m, f in all_published: + tool_handlers[m.name] = f + + publish( + metadata(name="find_tool", description=summary, + parameters=FIND_TOOL_SCHEMA, annotations={}), + lambda args: {"results": index.search(args["query"])}, + ) + publish( + metadata(name="call_tool", description="Call a tool by name.", + parameters=CALL_TOOL_SCHEMA, annotations={}), + lambda args: tool_handlers[args["tool_name"]](args["arguments"]), + ) +else: + # Standard mode: publish tools directly + for m, f in all_published: + publish(m, f) +``` + +This is approximately 80 lines of Starlark. It replaces ~2000 lines of Go across the aggregator, optimizer, and decorator stack. The POC will validate whether this sketch is complete and correct. ### Detailed Design @@ -539,33 +674,37 @@ The only exception is legacy declarative composite tools (`compositeTools`, `com ```mermaid sequenceDiagram + participant Agent as MCP Client + participant Authz as Authz Middleware + participant Session as MultiSession + participant Backend as Backend MCP Servers participant Factory as Session Factory - participant Cedar as Cedar Authz participant Script as Session Init Script - participant Session as MultiSession - participant Agent as MCP Client - Note over Factory: Session creation - Factory->>Factory: Load script (preset or generated from config) - Factory->>Cedar: Determine authorized backends for user - Cedar-->>Factory: Authorized backend set - - Factory->>Script: Execute script with authorized backends + Note over Factory: Session creation (once) + Factory->>Backend: Discover backend capabilities + Backend-->>Factory: Tools, resources, prompts + Factory->>Script: Execute session init script Script->>Script: backends() → iterate, filter, wrap, publish Script-->>Factory: Published (metadata, handler) set Factory->>Session: Construct MultiSession from published tools - Note over Agent: tools/list - Agent->>Session: tools/list - Session-->>Agent: Published tool metadata - - Note over Agent: tools/call - Agent->>Session: tools/call "find_tool" {query: "github"} - Session->>Session: Invoke published handler - Session-->>Agent: CallToolResult + Note over Agent: Runtime (per request) + Agent->>Authz: tools/list + Authz->>Session: tools/list + Session-->>Authz: Published tool metadata + Authz-->>Agent: Filtered tool metadata (unauthorized tools removed) + + Agent->>Authz: tools/call "find_tool" {query: "github"} + Authz->>Authz: Check authorization + Authz->>Session: Invoke published handler + Session->>Backend: Forward to backend + Backend-->>Session: Result + Session-->>Authz: CallToolResult + Authz-->>Agent: CallToolResult ``` -The script runs **once** per session, not per request. `publish()` calls build up the tool set. The resulting `(metadata, handler)` pairs are used to construct the `MultiSession`, which handles all subsequent `tools/list` and `tools/call` requests. +The script runs **once** per session, not per request. `publish()` calls build up the tool set. The resulting `(metadata, handler)` pairs are used to construct the `MultiSession`, which handles all subsequent `tools/list` and `tools/call` requests. The authz middleware sits between the agent and the session, filtering and gating requests at runtime. #### Where this fits in the architecture @@ -584,9 +723,16 @@ The session initialization script is not a decorator — it is used during sessi #### Interaction with authorization -The session initialization script runs after authorization has determined which backends and tools are available. `backends()` returns only what the user is authorized to use. `call_tool()` delegates to the base session, which enforces the routing table. The script cannot escalate privileges. +As described in [Background: How authorization works today](#background-how-authorization-works-today), the session initialization script runs during session construction — before the authz middleware. `backends()` returns all backends configured for the persona, regardless of the current user's authorization. + +The authz middleware continues to enforce authorization at runtime: + +- `tools/list` responses are filtered to remove tools the caller isn't authorized to use +- `tools/call` requests are gated — unauthorized calls return 403 -The specific mechanism for authorization (Cedar policies at the HTTP middleware layer, or a future alternative) is orthogonal to this design. Additional built-in functions could be added in the future to make the authorization integration more explicit within the script, but that is out of scope for this RFC. +This means publishing a tool via `publish()` does not bypass authorization. The script controls *what tools exist and how they behave*; the authz middleware controls *who can see and use them*. + +**Note**: Because the script sees all discovered tools, a handler could dispatch to tools the current user isn't authorized to use by saving handler references into a dict. This is the same trust model as composite tools today — administrators who write composite tool configurations can already wire calls to any backend tool. Fixing this boundary (moving authz before session construction) is out of scope for this RFC. #### Interaction with dynamic webhooks @@ -599,10 +745,10 @@ Both coexist. A request passes through webhooks first (external policy), then re #### Interaction with rate limiting -THV-0057's Redis-backed token bucket is the *mechanism*. `check_rate_limit()` exposes it to scripts. The *policy* can be: +THV-0057's Redis-backed token bucket is the *mechanism*. A future `check_rate_limit()` built-in could expose it to scripts. Once available, the *policy* could be: -1. **Config-driven**: The `standard` / `optimizer` presets read `rateLimiting` from `config` and call `check_rate_limit()` internally -2. **Script-driven**: Custom scripts implement context-aware rate limiting +1. **Config-driven**: The `default` preset reads `rateLimiting` from config and applies limits internally +2. **Script-driven**: Custom scripts implement context-aware rate limiting using the built-in ### API Changes @@ -611,9 +757,8 @@ THV-0057's Redis-backed token bucket is the *mechanism*. `check_rate_limit()` ex ```go type SessionInitConfig struct { // Preset is a named built-in session initialization script. - // One of: "passthrough", "standard", "optimizer". - // When empty and no Script/ScriptFile is set, the session init script - // is auto-generated from the existing aggregation/optimizer config. + // Currently only "default" is supported (reads existing config knobs). + // When empty and no Script/ScriptFile is set, the "default" preset is used. Preset string `json:"preset,omitempty" yaml:"preset,omitempty"` // Script is inline Starlark source. Mutually exclusive with Preset and ScriptFile. @@ -626,7 +771,7 @@ type SessionInitConfig struct { #### Existing config fields -`aggregation`, `compositeToolRefs`, and `optimizer` remain on `Config`. When no `sessionInit` block is present, they are used to auto-generate the session initialization script. When `sessionInit` is present, `aggregation` and `optimizer` are ignored (if both are set, vMCP logs a warning). +`aggregation`, `compositeToolRefs`, and `optimizer` remain on `Config`. The `default` preset reads these fields and produces identical behavior. When a custom `sessionInit.script` or `sessionInit.scriptFile` is set, these fields are ignored (if both are set, vMCP logs a warning). Legacy declarative composite tools (`compositeTools`, `compositeToolRefs`) are not supported in the session initialization script and will be removed in a future release. @@ -651,26 +796,25 @@ spec: | Threat | Description | Severity | |--------|-------------|----------| -| **Privilege escalation via script** | Script calls `call_tool()` for an unauthorized tool | High | +| **Privilege escalation via script** | Script handler dispatches to an unauthorized tool via saved references | Medium | | **Denial of service via infinite loop** | Script with `while True` or deep recursion | High | | **Tool list manipulation** | Script publishes tools that shouldn't be visible | Medium | -| **Decorator bypass** | Script omits `scrub_pii()` or `check_rate_limit()` | Medium | +| **Decorator bypass** | Script omits expected handler wrappers (e.g., scrubbing, rate limiting) | Medium | | **Resource exhaustion** | Script builds large data structures | High | ### Authentication and Authorization -**Cedar remains the authorization boundary.** The Starlark engine cannot circumvent it: +**Cedar remains the authorization boundary at runtime.** The authz middleware filters `tools/list` and gates `tools/call`: -- `backends()` returns only authorized backends and tools -- `call_tool()` delegates to the base session's `CallTool()`, which checks the routing table built from authorized tools only -- `publish()` can publish tools from `backends()` or new tools whose handlers use `call_tool()` — which is authorization-gated +- `backends()` returns all backends configured for the persona (not filtered by user authorization) +- `publish()` declares tools visible to the session, but the authz middleware filters/gates them at runtime +- Handlers can dispatch to any discovered tool via saved references — same trust model as composite tools today **Trust model**: Session initialization scripts are written by administrators, not end users. An administrator who can write a Starlark script already has the authority to configure vMCP. ### Data Security - Scripts cannot access filesystem, network, or environment variables (Starlark sandbox) -- `scrub_pii()` operates on the Go side with auditable patterns - Tool call results transit through handlers; administrators are trusted (same model as webhook config) ### Input Validation @@ -687,96 +831,147 @@ Scripts have no access to secrets. Backend authentication is handled below the s - Each `publish()` logged (tool name, source: backend or script-defined) - Each handler invocation logged (tool name, duration, outcome) -- Each `check_rate_limit()` logged (key, limit, decision) -- Each `scrub_pii()` logged (redaction count) +- Each built-in function invocation logged with relevant parameters - Each `elicit()` logged (prompt, action, duration) ### Mitigations | Threat | Mitigation | |--------|-----------| -| Privilege escalation | `tools()` and `call_tool()` are Cedar-gated | +| Privilege escalation | Authz middleware filters `tools/list` and gates `tools/call` at runtime; scripts are admin-authored | | DoS via loops | Execution step limit (default 1M), context timeout (same as THV-0051) | -| Tool list manipulation | `publish()` only surfaces tools from `tools()` or script-defined tools; audit logs record every call | -| Decorator bypass | Presets include scrubbing/rate limiting when configured; custom scripts are admin's responsibility | +| Tool list manipulation | `publish()` declares tools but authz middleware still filters at runtime; audit logs record every call | +| Decorator bypass | Custom scripts are admin's responsibility; presets include expected wrappers | | Resource exhaustion | Execution step limit, memory monitoring (same as THV-0051) | ## Alternatives Considered -### Alternative 1: Keep adding config knobs +### Configuration approaches + +#### Alternative 1: Keep adding config knobs - **Pros**: No new concepts for simple cases - **Cons**: Interaction matrix grows quadratically. Bugs like #4287 from non-obvious interactions. Testing becomes intractable. - **Why not chosen**: Already causing problems at current feature count. -### Alternative 2: Starlark for composite tools only (THV-0051 as-is) +#### Alternative 2: Declarative pipeline (ordered stages) + +Instead of a scripting language, make the config ordering explicit — a pipeline of named stages (like Envoy filter chains or Traefik middleware stacks). + +- **Pros**: Declarative, no scripting language to learn, explicit ordering solves the interaction problem. +- **Cons**: A declarative pipeline can express ordering and filtering, but cannot express computed values (dynamic descriptions based on available tools), conditional logic (different behavior based on annotations), or new synthetic tools (a `find_tool` with a generated description). Every new behavior still requires a new stage type implemented in Go. +- **Why not chosen**: The problem isn't just ordering — it's that administrators need to express logic that varies per deployment. A pipeline makes ordering explicit but keeps the "new knob per behavior" problem. + +#### Alternative 3: Starlark for composite tools only (THV-0051 as-is) - **Pros**: Smaller scope - **Cons**: Misses the opportunity to unify. Interaction problem remains for optimizer + filter + rate limiting. Expanding scope later means a second migration. - **Why not chosen**: Design for the broader use case from day one. -### Alternative 3: Use webhooks for everything +#### Alternative 4: Use webhooks for everything - **Pros**: Maximum flexibility, language-agnostic - **Cons**: External services for simple policies. Network latency on every call. Overkill for "hide these tools." - **Why not chosen**: Webhooks for external integration, Starlark for internal configuration. Both should exist. -### Alternative 4: OPA / Rego instead of Starlark +### Language considerations + +#### Why Starlark + +[Starlark](https://github.com/bazelbuild/starlark/blob/master/spec.md) is a Python-like language designed for embedding in Go applications. It was created for Bazel's build configuration and is used by Buck2, Tilt, Drone CI, and other infrastructure tools. Key properties: + +- **Deterministic**: No I/O, no threads, no randomness. The only side effects are the built-ins we provide. +- **Sandboxed**: No filesystem, network, or environment variable access by design. +- **Familiar syntax**: Python-like, readable by anyone who's seen Python. +- **Mature Go implementation**: [google/starlark-go](https://github.com/google/starlark-go) is well-maintained and battle-tested. +- **Resource limits**: Execution step limits prevent infinite loops. + +#### Alternative: OPA / Rego - **Pros**: Established policy language - **Cons**: Rego is for boolean decisions (allow/deny), not programmatic composition. Expressing "publish a search tool with a dynamic description" would be extremely awkward. We already use Cedar for authz. - **Why not chosen**: Wrong abstraction — we need a programming model, not a policy language. +#### Alternative: Risor + +[Risor](https://risor.io/) is a Go-native scripting language with richer features (try/catch, goroutines, Go stdlib access). + +- **Pros**: More expressive, has exception handling, familiar Go-like stdlib. +- **Cons**: Go stdlib access is a security risk requiring extensive auditing. Younger project with smaller community. Extra features (goroutines, classes) are unnecessary complexity. Less battle-tested for embedded sandboxed use. +- **Why not chosen**: Starlark's restrictions are features for our use case. We want a language where the only side effects are the built-ins we provide. + +#### Alternative: WebAssembly (Wasm) plugins + +- **Pros**: Language-agnostic, strong sandboxing via Wasm runtime. +- **Cons**: Massive complexity increase (Wasm runtime, host function bindings, memory management). Poor developer experience (compile step, no REPL, opaque errors). Overkill for tool orchestration scripts. +- **Why not chosen**: The problem is configuring tool behavior, not running arbitrary compute. Starlark is the right level of abstraction. + +#### Alternative: Lua (OpenResty / Envoy model) + +- **Pros**: Proven in proxy scripting (Nginx/OpenResty, Kong, Envoy). Large ecosystem. +- **Cons**: Lua has mutable global state, unrestricted I/O by default — sandboxing requires careful auditing. `1`-indexed arrays are a footgun. Go bindings (gopher-lua) are less mature than starlark-go. Not deterministic without effort. +- **Why not chosen**: Starlark provides sandboxing and determinism by default. Lua requires building those guarantees on top. + ## Compatibility ### Backward Compatibility -All existing config fields (`aggregation`, `optimizer`) continue to produce identical behavior. Internally, they are translated into a session initialization script rather than running through a separate legacy code path. Users can run `thv vmcp migrate-config` to see and adopt the generated script. +All existing config fields (`aggregation`, `optimizer`) continue to produce identical behavior. The `default` preset reads these fields and produces the same behavior as the current config-driven system. There is no separate legacy code path. The exception is legacy declarative composite tools (`compositeTools`, `compositeToolRefs`), which are replaced by Starlark scripted tools from THV-0051. ### Forward Compatibility -New built-in functions can be added without breaking existing scripts. New presets can be added alongside existing ones. The `config` map on presets uses the same types as existing config, so new config fields are automatically available. +New built-in functions can be added without breaking existing scripts. New presets can be added alongside existing ones. ## Implementation Plan -### Phase 1: Feature parity — session initialization replaces existing decorators +### Phase 1: Proof of concept -The first deliverable must produce identical behavior to the existing config-driven system (except for legacy composite tools). This is the critical migration gate. +A fast, rough POC to validate the high-level design. The goal is to prove the programming model works end-to-end and surface any surprises before committing to a production implementation. -- Extend the Starlark engine from THV-0051 with `backends()`, `publish()`, `metadata()`, `get_tool()` built-ins +- Implement `backends()`, `publish()`, `metadata()` built-ins in the Starlark engine - Session factory runs the script and constructs `MultiSession` from `publish()` results +- Implement the `default` preset that reads existing config knobs (`aggregation`, `optimizer`, etc.) +- **Delete** the existing decorator-based code that supports these config knobs today (optimizer, filter, composite tools decorators) +- All existing tests must pass **except** those that test legacy composite tools (`compositeTools`, `compositeToolRefs`) +- Update this RFC with any findings — design changes, missing built-ins, edge cases discovered + +### Phase 2: Production implementation + +Take the learnings from the POC and implement for real. This is the production-quality version with proper error handling, tests, and documentation. + +- Extend the Starlark engine from THV-0051 with production-quality `backends()`, `publish()`, `metadata()` built-ins - Port `search_index()` from current optimizer implementation -- Implement `passthrough`, `standard`, and `optimizer` presets -- Auto-generate session init script from existing `aggregation` / `optimizer` config when no `sessionInit` block is present -- `thv vmcp migrate-config` command to output the generated script - `thv vmcp show-preset` command to inspect built-in presets - Config model: `sessionInit.preset`, `sessionInit.script`, `sessionInit.scriptFile` -- Preset equivalence tests: verify every preset produces identical behavior to the old config-driven feature it replaces +- Preset equivalence tests: verify the `default` preset produces identical behavior to the old config-driven system - Remove optimizer, filter, and composite tools decorators — the session init script is the single implementation -### Phase 2: New capabilities +### Phase 3: Deprecate composite tools + +- Mark `compositeTools` and `compositeToolRefs` as deprecated +- Log deprecation warnings when these fields are used +- Document migration path from declarative composite tools to Starlark scripts -These are net-new built-in functions that make new use cases *possible*. The scope of this phase is to add the built-in functions, not to ship fully-featured implementations. +### Phase 4: Ship and document -- Implement `scrub_pii()` built-in -- Implement `check_rate_limit()` built-in (backed by THV-0057's Redis token bucket when available) - E2E tests for custom scripts in K8s via ConfigMap -- Documentation: user guide, built-in reference, migration guide +- Documentation: user guide, built-in reference, composite tools migration guide, advanced use cases + +New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of scope for this RFC. The programming model makes them straightforward to add as follow-up work. ### Dependencies -- THV-0051 (Starlark engine core) — base engine, value converter, `call_tool`, `try_call_tool`, `retry`, `parallel`, `elicit`, `log` -- THV-0057 (rate limiting) — Redis token bucket for `check_rate_limit()` (Phase 2 only) +- THV-0051 (Starlark engine core) — base engine, value converter, `elicit`, `log` ## Testing Strategy - **Unit tests**: Each built-in in isolation. Handler wrapping / function composition. `publish()` validation. Preset loading and config injection. -- **Integration tests**: Full script execution with mock backends. Decorator chains. Composite tool handlers via `call_tool()`. Optimizer pattern with `search_index()`. -- **E2E tests**: Preset configuration in K8s. Custom scripts via ConfigMap. Old config → session init migration. -- **Security tests**: `tools()` respects Cedar. `call_tool()` rejects unauthorized tools. Step limits. Memory. -- **Preset equivalence tests**: For each preset, verify behavior matches the old config-driven feature it replaces. +- **Integration tests**: Full script execution with mock backends. Handler wrapping chains. Composite tool handlers via saved references. Optimizer pattern with `search_index()`. +- **E2E tests**: Preset configuration in K8s. Custom scripts via ConfigMap. +- **Security tests**: Authz middleware filters published tools correctly. Step limits. Memory. +- **Preset equivalence tests**: Verify the `default` preset produces identical behavior to the old config-driven system. ## Documentation @@ -788,13 +983,9 @@ These are net-new built-in functions that make new use cases *possible*. The sco ## Open Questions -1. **Should presets be composable?** Could a user layer multiple presets, or is a single preset sufficient? Multiple presets add complexity in ordering and config conflicts. - -2. **Hot reloading**: Should ConfigMap updates to scripts trigger live session recreation? Convenient but complex (re-validation, in-flight calls). - -3. **Custom PII patterns**: Should `scrub_pii()` accept custom regex patterns (e.g., internal employee ID formats), or is the built-in set sufficient? +1. **Hot reloading**: Should ConfigMap updates to scripts trigger live session recreation? Convenient but complex (re-validation, in-flight calls). -4. **Error handling in handler functions**: When a handler function calls `call_tool()` and it fails, the script halts and the agent receives an error. How should administrators configure error handling behavior? Options include: (a) `try_call_tool()` for opt-in error handling (already in THV-0051), (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). +2. **Error handling in handler functions**: When a handler function invokes a backend tool and it fails, what should happen? Options include: (a) the handler returns an error dict to the agent, (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). ## References From e13d6877ef27f7c3555993f9d7b06c723a71aacf Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 25 Mar 2026 18:00:09 -0700 Subject: [PATCH 04/19] Add optimizer/Cedar incompatibility example and authz open question Reference stacklok/toolhive#4373 as a concrete example of feature interaction pain in the Problem Statement. Add open question about whether authz decisions should move into Starlark to unify the "who sees what?" model. Co-Authored-By: Claude Opus 4.6 --- rfcs/THV-0059-starlark-programmable-middleware.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfcs/THV-0059-starlark-programmable-middleware.md b/rfcs/THV-0059-starlark-programmable-middleware.md index a5764cb..a75f2b6 100644 --- a/rfcs/THV-0059-starlark-programmable-middleware.md +++ b/rfcs/THV-0059-starlark-programmable-middleware.md @@ -16,7 +16,7 @@ Introduce a Starlark-based session initialization script for vMCP. A single scri ### Config knob combinations -vMCP's feature set is growing. Each feature has arrived with its own configuration surface. The problem is not just the number of knobs, but that they have subtle dependencies on each other: conflict resolution and aggregation change tool names, filtering changes which tools are available at different points in the pipeline, and downstream config blocks (rate limiting, composite tools) must reference tool names that earlier config blocks may have renamed or removed. The result is that configuring one feature correctly requires understanding the side effects of every other feature: +vMCP's feature set is growing. Each feature has arrived with its own configuration surface. The problem is not just the number of knobs, but that they have subtle dependencies on each other: conflict resolution and aggregation change tool names, filtering changes which tools are available at different points in the pipeline, and downstream config blocks (rate limiting, composite tools) must reference tool names that earlier config blocks may have renamed or removed. The result is that configuring one feature correctly requires understanding the side effects of every other feature. A concrete example: enabling the optimizer replaces real tool names with `find_tool` / `call_tool` meta-tools, which silently breaks Cedar policies that reference the original names ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373)). | Feature | Config surface | Introduced in | |---------|---------------|---------------| @@ -987,6 +987,8 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc 2. **Error handling in handler functions**: When a handler function invokes a backend tool and it fails, what should happen? Options include: (a) the handler returns an error dict to the agent, (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). +3. **Should authz decisions move into Starlark?** Authorization (Cedar) and session initialization (Starlark) remain entirely separate systems. This RFC reduces config knob interactions significantly and makes most of them explicit, but the "who sees what?" question still requires reasoning across both systems. The optimizer/Cedar incompatibility ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373)) is one example — the script rewrites tool names that Cedar policies reference, and neither system is aware of the other. Pulling authz decisions into the script (e.g., a `current_user()` built-in combined with policy logic) would unify the model but raises questions about Cedar's role and the trust boundary. Worth exploring once the base programming model is proven. + ## References - [THV-0051: Starlark Scripted Tools](./THV-0051-starlark-scripted-tools.md) — original Starlark RFC From e52c4ce26b4679014b68079bab0a799f57058a2d Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 25 Mar 2026 18:03:14 -0700 Subject: [PATCH 05/19] Add current_user() to future built-ins list in use cases intro Co-Authored-By: Claude Opus 4.6 --- rfcs/THV-0059-starlark-programmable-middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/THV-0059-starlark-programmable-middleware.md b/rfcs/THV-0059-starlark-programmable-middleware.md index a75f2b6..bb6ef4d 100644 --- a/rfcs/THV-0059-starlark-programmable-middleware.md +++ b/rfcs/THV-0059-starlark-programmable-middleware.md @@ -250,7 +250,7 @@ publish( ### Motivating Use Cases -The following use cases illustrate what the programming model enables. Use Cases 1, 2, and 4 are achievable with the v0 built-ins. Use Cases 3, 5, and 6 depend on future built-ins (`scrub_pii()`, `check_rate_limit()`) that are not in scope for this RFC but demonstrate why the model is worth building. +The following use cases illustrate what the programming model enables. Use Cases 1, 2, and 4 are achievable with the v0 built-ins. Use Cases 3, 5, and 6 depend on future built-ins (`current_user()`, `scrub_pii()`, `check_rate_limit()`) that are not in scope for this RFC but demonstrate why the model is worth building. #### Use Case 1: Dynamic optimizer descriptions From cf7b52d4ca0c0a03775806c694677b923310a2ec Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 25 Mar 2026 18:59:11 -0700 Subject: [PATCH 06/19] my edits --- ...V-0059-starlark-programmable-middleware.md | 99 ++++--------------- 1 file changed, 17 insertions(+), 82 deletions(-) diff --git a/rfcs/THV-0059-starlark-programmable-middleware.md b/rfcs/THV-0059-starlark-programmable-middleware.md index bb6ef4d..a6b18ce 100644 --- a/rfcs/THV-0059-starlark-programmable-middleware.md +++ b/rfcs/THV-0059-starlark-programmable-middleware.md @@ -31,24 +31,19 @@ vMCP's feature set is growing. Each feature has arrived with its own configurati Each knob is individually reasonable. The problem is their **interaction**. Today: -- The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static — agents don't know what tools they might find behind it (see [Slack thread](https://stacklok.slack.com/archives/C09L9QF47EU/p1774392171855569)). - The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287) (RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is). - Rate limiting (THV-0057) adds per-tool limits via yet another config block that must reference the same tool names that may have been renamed by overrides. - There is no mechanism to express cross-cutting policies like "tools without a `readOnly` annotation must only be invokable via a composite tool that includes an elicitation step." +- The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static. We've discussed many solutions on [this issue](https://github.com/stacklok/toolhive/issues/4357). To support them all, we'd have to add many more config knobs. As Alejandro comment points out too, we'd also like the solutions to not be one-size fits all. There are valid reasons for allowing more configurability, but that comes at the cost of cognitive load for operators and maintainers. Every new capability doubles the interaction matrix. Administrators who need non-trivial configurations must understand the ordering and interaction of all these knobs — a burden that scales poorly. -### The optimizer discoverability problem - -The Slack thread on optimizer quality highlights a concrete symptom: agents don't use `find_tool` because its description doesn't tell them what tools are available behind it. The proposed fix — dynamically generating `find_tool`'s description based on available tools — is a special case of a general need: the ability to programmatically control what the agent sees and how it's described. - -A config knob for "optimizer description template" would fix this one case. But the next request will be "I want the optimizer to group tools by category" or "I want different descriptions per persona." Each becomes another knob. ### Who is affected - **Platform administrators** who configure vMCP for multi-tenant deployments and need predictable behavior from feature combinations. - **Enterprise integrators** who need custom policies (PII scrubbing, approval workflows, tool restrictions) but don't want to fork ToolHive or maintain webhook services for simple logic. -- **The vMCP development team** who must reason about the interaction of every new feature with every existing feature. +- **The vMCP maintainers** who must reason about the interaction of every new feature with every existing feature. ### Why this is worth solving now @@ -71,16 +66,16 @@ Critically, the authz middleware operates on the *final published tool set* — 2. Published tools are still subject to authz filtering/gating at runtime — publishing a tool does not bypass authorization. 3. A handler could dispatch to any discovered tool via saved references, including tools the current user isn't authorized to use. This is the same trust model as composite tools today — see [Interaction with authorization](#interaction-with-authorization). -Fixing the authz boundary is out of scope for this RFC. +Modifying the authz boundary is out of scope for this RFC. ## Goals - Define a Starlark-based programming model that subsumes tool advertising, renaming, optimizer behavior, and composite tool workflows into a single script that runs once per session - Make it easy to add new capabilities (search indexing, PII scrubbing, rate limiting) as simple built-in functions rather than config knobs with complex interactions - Preserve the existing authorization boundary — Cedar authz middleware continues to filter `tools/list` and gate `tools/call` at runtime, independent of what the script publishes -- Make the system accessible to non-power-users via built-in presets that replicate today's config-driven behavior +- Make the system accessible to non-power-users by preserving the configuration that we have today. - Enable policies that span multiple features (e.g., "non-readonly tools require elicitation") -- Maintain full backward compatibility with existing config fields — the session initialization script must be able to replicate every behavior currently achievable via `aggregation`, `optimizer`, and related config (except legacy composite tools, which are replaced by Starlark scripts from THV-0051) +- Maintain full backward compatibility with existing config fields — the session initialization script must be able to replicate every behavior currently achievable via `aggregation`, `optimizer`, and related config (except legacy composite tools, which are replaced by Starlark scripts). The plan for legacy composite tools is discussed in the implementation plan below. ## Non-Goals @@ -94,7 +89,7 @@ Fixing the authz boundary is out of scope for this RFC. ### High-Level Design -A vMCP persona runs a single Starlark **session initialization script** once per session. The script receives discovered backends via `backends()` — a dict keyed by backend name, where each value exposes the backend's tools, resources, and prompts. The script calls `publish()` to declare what the agent sees. +A vMCP caller runs a single Starlark **session initialization script** once per session. The script receives discovered backends via `backends()` — a dict keyed by backend name, where each value exposes the backend's tools, resources, and prompts. The script calls `publish()` to declare what the agent sees. ```mermaid flowchart TB @@ -109,7 +104,7 @@ flowchart TB backends_fn["backends() → dict of backend objects"] publish_fn["publish(metadata, handler)"] search_fn["search_index(tools) → index"] - config_fn["config() → persona config dict"] + config_fn["config() → config dict"] elicit_fn["elicit(message, schema) → decision"] end @@ -124,7 +119,7 @@ flowchart TB style Authz fill:#ffcc80 ``` -**Key invariant**: `backends()` returns all backends configured for the persona. The script publishes tools, and the authz middleware filters `tools/list` responses and gates `tools/call` requests at runtime. Publishing a tool does not bypass authorization. +**Key invariant**: `backends()` returns all backends configured. The script publishes tools, and the authz middleware filters `tools/list` responses and gates `tools/call` requests at runtime. Publishing a tool does not bypass authorization. ### The Programming Model @@ -152,18 +147,9 @@ for name, backend in backends().items(): publish(meta, fn) ``` -#### Filtering tools - -```python -for name, backend in backends().items(): - for meta, fn in backend.tools(): - if not meta.name.startswith("internal_"): - publish(meta, fn) -``` - #### Handling name collisions across backends -Because the script sees which backend each tool comes from, it can handle collisions explicitly — no need for a separate `conflictResolution` config: +Because the script sees which backend each tool comes from, it can handle collisions explicitly: ```python for name, backend in backends().items(): @@ -179,23 +165,6 @@ for name, backend in backends().items(): publish(meta, fn) ``` -#### Renaming tools - -`metadata` is a simple struct. To rename, create a new metadata explicitly passing all fields — `annotations` and `parameters` are required to prevent accidentally dropping them: - -```python -for name, backend in backends().items(): - for meta, fn in backend.tools(): - if meta.name == "pg_query": - publish( - metadata(name="database_query", description="Query the production database", - parameters=meta.parameters, annotations=meta.annotations), - fn, - ) - else: - publish(meta, fn) -``` - #### Decorating handlers Since handlers are just functions, decoration is plain function wrapping: @@ -215,22 +184,6 @@ for name, backend in backends().items(): publish(meta, with_pii_scrubbing(fn)) ``` -Decorators compose naturally: - -```python -for name, backend in backends().items(): - for meta, fn in backend.tools(): - wrapped = fn - wrapped = with_rate_limit(wrapped, meta.name) - wrapped = with_pii_scrubbing(wrapped) - - if not meta.annotations.get("readOnly", False): - wrapped = with_approval_gate(wrapped, meta.name) - - publish(meta, wrapped) -``` - -The outermost wrapper runs first. This is just function composition — no special framework. #### Defining new tools @@ -507,7 +460,7 @@ These are Go-implemented functions exposed to Starlark scripts. | Built-in | Signature | Description | |----------|-----------|-------------| -| `backends()` | `backends() → dict[string, Backend]` | Returns all backends configured for the persona, keyed by name. Each `Backend` object exposes `.tools()` (returns `list[(metadata, handler)]`), `.resources()`, and `.prompts()`. | +| `backends()` | `backends() → dict[string, Backend]` | Returns all backends configured, keyed by name. Each `Backend` object exposes `.tools()` (returns `list[(metadata, handler)]`), `.resources()`, and `.prompts()`. | | `publish(meta, handler)` | `publish(metadata, callable) → None` | Adds a tool to the set visible to the agent. `handler` receives a single `dict` argument. Authz middleware still filters/gates at runtime. | | `metadata(...)` | `metadata(name, description, parameters, annotations) → metadata` | Creates a new metadata struct. All four fields are required — this prevents accidentally dropping `annotations` or `parameters` when renaming. | @@ -517,7 +470,7 @@ These are Go-implemented functions exposed to Starlark scripts. |----------|-----------|-------------| | `search_index(tools)` | `search_index(list[(metadata, handler)]) → SearchIndex` | Builds a semantic search index over the tool list. Returns an object with `.search(query) → list[dict]`. | | `elicit(message, schema)` | `elicit(message, schema={}) → struct(action, content)` | Prompts the user for a decision via MCP elicitation. | -| `config()` | `config() → dict` | Returns the vMCP persona's config fields (`aggregation`, `optimizer`, etc.) as a read-only dict. Used by the `default` preset. | +| `config()` | `config() → dict` | Returns the vMCP config fields (`aggregation`, `optimizer`, etc.) as a read-only dict. Used by the `default` preset. | | `log(message)` | `log(message) → None` | Emits a structured audit log entry. | #### Handler options @@ -546,7 +499,7 @@ The programming model makes it straightforward to add new capabilities as simple ### Presets: Making it Easy for Non-Power-Users -The critical question is: how do people who don't want to write Starlark still use vMCP? +An important question is: how do people who don't want to write Starlark still use vMCP? **Answer: presets.** A preset is a named, built-in Starlark script that replicates the behavior of today's config knobs. Presets are transparent — users can inspect the underlying Starlark source and fork it when they need customization: @@ -714,7 +667,7 @@ The session initialization script replaces the current decorator stack for tool- Current model: New model: optimizer decorator Session factory runs - filter decorator session init script, + session init script, composite tools decorator constructs MultiSession base session from publish() results ``` @@ -723,7 +676,7 @@ The session initialization script is not a decorator — it is used during sessi #### Interaction with authorization -As described in [Background: How authorization works today](#background-how-authorization-works-today), the session initialization script runs during session construction — before the authz middleware. `backends()` returns all backends configured for the persona, regardless of the current user's authorization. +As described in [Background: How authorization works today](#background-how-authorization-works-today), the session initialization script runs during session construction — before the authz middleware. `backends()` returns all backends configured, regardless of the current user's authorization. The authz middleware continues to enforce authorization at runtime: @@ -775,20 +728,6 @@ type SessionInitConfig struct { Legacy declarative composite tools (`compositeTools`, `compositeToolRefs`) are not supported in the session initialization script and will be removed in a future release. -#### New CRD - -`VirtualMCPSessionInitScript` — references a Starlark session initialization script from a ConfigMap: - -```yaml -apiVersion: toolhive.stacklok.com/v1alpha1 -kind: VirtualMCPSessionInitScript -metadata: - name: my-org-session-init -spec: - configMapRef: - name: vmcp-session-init-scripts - key: init.star -``` ## Security Considerations @@ -961,9 +900,6 @@ Take the learnings from the POC and implement for real. This is the production-q New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of scope for this RFC. The programming model makes them straightforward to add as follow-up work. -### Dependencies - -- THV-0051 (Starlark engine core) — base engine, value converter, `elicit`, `log` ## Testing Strategy @@ -979,15 +915,14 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc - **Preset reference**: What each preset does, `show-preset` and forking - **Migration guide**: From old config knobs to session init presets or custom scripts - **Architecture docs**: Updated vMCP architecture with session initialization model -- **CRD reference**: `VirtualMCPSessionInitScript` ## Open Questions -1. **Hot reloading**: Should ConfigMap updates to scripts trigger live session recreation? Convenient but complex (re-validation, in-flight calls). +1. **Error handling in handler functions**: When a handler function invokes a backend tool and it fails, what should happen? Options include: (a) the handler returns an error dict to the agent, (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). -2. **Error handling in handler functions**: When a handler function invokes a backend tool and it fails, what should happen? Options include: (a) the handler returns an error dict to the agent, (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). +2. **Should authz decisions move into Starlark?** Authorization (Cedar) and session initialization (Starlark) remain entirely separate systems. This RFC reduces config knob interactions significantly and makes most of them explicit, but the "who sees what?" question still requires reasoning across both systems. The optimizer/Cedar incompatibility ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373)) is one example — the script rewrites tool names that Cedar policies reference, and neither system is aware of the other. Pulling authz decisions into the script (e.g., a `current_user()` built-in combined with policy logic) would unify the model but raises questions about Cedar's role and the trust boundary. Worth exploring once the base programming model is proven. -3. **Should authz decisions move into Starlark?** Authorization (Cedar) and session initialization (Starlark) remain entirely separate systems. This RFC reduces config knob interactions significantly and makes most of them explicit, but the "who sees what?" question still requires reasoning across both systems. The optimizer/Cedar incompatibility ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373)) is one example — the script rewrites tool names that Cedar policies reference, and neither system is aware of the other. Pulling authz decisions into the script (e.g., a `current_user()` built-in combined with policy logic) would unify the model but raises questions about Cedar's role and the trust boundary. Worth exploring once the base programming model is proven. +3. What happens when MCP supports requests without sessions? Do we have to run this heavy script on every request? We could actually run the script once at startup, since it does not depend on request-time information. However, if we fold in authz concerns from above, then `current_user()` will be request-time information. We could cheat around this by recommending all logic which depends on `current_user()` be placed at the end of the script. When that's encountered during startup, we block and restore the state on each request. Alternatively, we could support two different scripts. One for initialization and one per-request. ## References From 69f0ba1df5f3286b9e4bd28e0d306a352f3387f1 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 25 Mar 2026 19:01:06 -0700 Subject: [PATCH 07/19] Rename RFC from THV-0059 to THV-0060 to match PR number Co-Authored-By: Claude Opus 4.6 --- ...ddleware.md => THV-0060-starlark-programmable-middleware.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename rfcs/{THV-0059-starlark-programmable-middleware.md => THV-0060-starlark-programmable-middleware.md} (99%) diff --git a/rfcs/THV-0059-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md similarity index 99% rename from rfcs/THV-0059-starlark-programmable-middleware.md rename to rfcs/THV-0060-starlark-programmable-middleware.md index a6b18ce..61a42f9 100644 --- a/rfcs/THV-0059-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -1,4 +1,4 @@ -# THV-0059: Starlark Session Initialization for vMCP +# THV-0060: Starlark Session Initialization for vMCP - **Status**: Draft - **Author(s)**: Jeremy Drouillard (@jerm-dro) From 6dc67ff050c0f4e477c65be2c125da6d7e2b1308 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 25 Mar 2026 19:14:09 -0700 Subject: [PATCH 08/19] Move authz/optimizer examples to open question, add #4374 The optimizer/Cedar issues (#4373, #4374) are about the authz boundary, not config knob combinations. Move them to Open Question 2 where they motivate pulling authz into Starlark. Use #4287 as the Problem Statement example instead. Co-Authored-By: Claude Opus 4.6 --- rfcs/THV-0060-starlark-programmable-middleware.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index 61a42f9..8445e53 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -16,7 +16,7 @@ Introduce a Starlark-based session initialization script for vMCP. A single scri ### Config knob combinations -vMCP's feature set is growing. Each feature has arrived with its own configuration surface. The problem is not just the number of knobs, but that they have subtle dependencies on each other: conflict resolution and aggregation change tool names, filtering changes which tools are available at different points in the pipeline, and downstream config blocks (rate limiting, composite tools) must reference tool names that earlier config blocks may have renamed or removed. The result is that configuring one feature correctly requires understanding the side effects of every other feature. A concrete example: enabling the optimizer replaces real tool names with `find_tool` / `call_tool` meta-tools, which silently breaks Cedar policies that reference the original names ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373)). +vMCP's feature set is growing. Each feature has arrived with its own configuration surface. The problem is not just the number of knobs, but that they have subtle dependencies on each other: conflict resolution and aggregation change tool names, filtering changes which tools are available at different points in the pipeline, and downstream config blocks (rate limiting, composite tools) must reference tool names that earlier config blocks may have renamed or removed. The result is that configuring one feature correctly requires understanding the side effects of every other feature. A concrete example: the advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287) — the fact that the bug existed shows how opaque the interaction is. | Feature | Config surface | Introduced in | |---------|---------------|---------------| @@ -920,7 +920,7 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc 1. **Error handling in handler functions**: When a handler function invokes a backend tool and it fails, what should happen? Options include: (a) the handler returns an error dict to the agent, (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). -2. **Should authz decisions move into Starlark?** Authorization (Cedar) and session initialization (Starlark) remain entirely separate systems. This RFC reduces config knob interactions significantly and makes most of them explicit, but the "who sees what?" question still requires reasoning across both systems. The optimizer/Cedar incompatibility ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373)) is one example — the script rewrites tool names that Cedar policies reference, and neither system is aware of the other. Pulling authz decisions into the script (e.g., a `current_user()` built-in combined with policy logic) would unify the model but raises questions about Cedar's role and the trust boundary. Worth exploring once the base programming model is proven. +2. **Should authz decisions move into Starlark?** Authorization (Cedar) and session initialization (Starlark) remain entirely separate systems. This RFC reduces config knob interactions significantly and makes most of them explicit, but the "who sees what?" question still requires reasoning across both systems. The interaction between the optimizer and Cedar illustrates the problem: enabling the optimizer replaces real tool names with `find_tool` / `call_tool`, which silently breaks Cedar policies that reference the original names ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373))); and `find_tool` returns tools the caller isn't authorized to use, because Cedar gates `tools/call` but doesn't filter search results inside a handler ([stacklok/toolhive#4374](https://github.com/stacklok/toolhive/issues/4374)). Neither system is aware of the other. Pulling authz decisions into the script (e.g., a `current_user()` built-in combined with policy logic) would unify the model but raises questions about Cedar's role and the trust boundary. Worth exploring once the base programming model is proven. 3. What happens when MCP supports requests without sessions? Do we have to run this heavy script on every request? We could actually run the script once at startup, since it does not depend on request-time information. However, if we fold in authz concerns from above, then `current_user()` will be request-time information. We could cheat around this by recommending all logic which depends on `current_user()` be placed at the end of the script. When that's encountered during startup, we block and restore the state on each request. Alternatively, we could support two different scripts. One for initialization and one per-request. From 54174d15113c3cbadd609c8ff3a063e9ee61695c Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 17:25:52 -0700 Subject: [PATCH 09/19] Add prior art section and feature dependency diagram Add "Prior Art: Gateway Configurability Patterns" covering Envoy, Kong, and the Configuration Complexity Clock. Add vMCP feature dependency diagram to the Problem Statement. Co-Authored-By: Claude Opus 4.6 --- ...HV-0060-starlark-programmable-middleware.md | 14 +++++++++++++- rfcs/images/vmcp-feature-dependencies.png | Bin 0 -> 285879 bytes 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 rfcs/images/vmcp-feature-dependencies.png diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index 8445e53..b22ddba 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -36,7 +36,11 @@ Each knob is individually reasonable. The problem is their **interaction**. Toda - There is no mechanism to express cross-cutting policies like "tools without a `readOnly` annotation must only be invokable via a composite tool that includes an elicitation step." - The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static. We've discussed many solutions on [this issue](https://github.com/stacklok/toolhive/issues/4357). To support them all, we'd have to add many more config knobs. As Alejandro comment points out too, we'd also like the solutions to not be one-size fits all. There are valid reasons for allowing more configurability, but that comes at the cost of cognitive load for operators and maintainers. -Every new capability doubles the interaction matrix. Administrators who need non-trivial configurations must understand the ordering and interaction of all these knobs — a burden that scales poorly. +Every new capability doubles the interaction matrix. The following diagram maps the dependencies between vMCP features ([excalidraw source](https://excalidraw.com/#json=C3Co-yHQMwzjrJptY7Qmv,mCqdzvMmerb6yZ0_gmt24g)): + +![vMCP feature dependency graph](./images/vmcp-feature-dependencies.png) + +Administrators who need non-trivial configurations must understand the ordering and interaction of all these knobs — a burden that scales poorly. ### Who is affected @@ -51,6 +55,14 @@ THV-0051 proposes Starlark for composite tools. Before that engine ships, we sho The cost equation also favors acting now. As more capabilities are added, the cost of retrofitting a composable system increases — more code to replace, more interactions to preserve. Meanwhile, the cost of building each new config knob is *also* increasing, because each knob must reason about its interactions with every existing knob and implement more than a simple built-in. A session initialization model inverts this: new capabilities ship as simple built-in functions, and administrators compose them as needed. The longer we wait, the more expensive both paths become. +### Prior Art: Gateway Configurability Patterns + +Envoy Proxy faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal Lua filter, WASM filters, and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its ext_authz filter, with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. + +Kong Gateway built its plugin architecture on Lua lifecycle callbacks with a Plugin Development Kit exposing built-in functions for request inspection and response control. The pattern — built-in functions as mechanism, user scripts as policy — directly informs vMCP's `publish()` + handler model. + +The [Configuration Complexity Clock](https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html) (Hadlow, 2012) describes the lifecycle this RFC interrupts: hard-coded values → config file → complex config → rules engine → DSL → "essentially a programming language, except crappier." vMCP's config knob interactions are at the "complex config" stage. The session initialization model jumps to a real programming language with proper semantics, rather than waiting for the config surface to accumulate ad-hoc conditionals that amount to a worse one. + ### Background: How authorization works today Authorization in vMCP is enforced by authz middleware that sits between the client and the session: diff --git a/rfcs/images/vmcp-feature-dependencies.png b/rfcs/images/vmcp-feature-dependencies.png new file mode 100644 index 0000000000000000000000000000000000000000..ec95df0c0bdc22ab686a1030d2b99107c69953c9 GIT binary patch literal 285879 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
    Dd14wf)==16x#UR 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¥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 z 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

    XRTdaYxcD- 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%0Aqp|Eyr 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

    K(oMAJ{y+Qp$VIm9C9UYBc#21e1S#xcujTcNj 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 PhX*e!Z*8``3#{iDUQre#)zJ%knQtmNz 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�!dwT~+uQ6Z@G;sgY#q9{^= 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 literal 0 HcmV?d00001 From c9a4cdcdfb1f863cfb29b7f55eea5393f9302f50 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 17:42:00 -0700 Subject: [PATCH 10/19] Restructure Problem Statement and move Background under Proposed Solution Split monolithic "Config knob combinations" into two focused sections: "The configurability problem" (user-facing interaction examples) and "The maintainability problem" (developer-facing quadratic cost). Move Background (prior art + authz) under Proposed Solution where it provides context for the design. Co-Authored-By: Claude Opus 4.6 --- ...V-0060-starlark-programmable-middleware.md | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index b22ddba..9f5a209 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -14,9 +14,26 @@ Introduce a Starlark-based session initialization script for vMCP. A single scri ## Problem Statement -### Config knob combinations +vMCP's feature set is growing, and two forces are pulling against each other: users want more configurability, maintainers need the system to stay understandable. Each new capability makes both sides worse. -vMCP's feature set is growing. Each feature has arrived with its own configuration surface. The problem is not just the number of knobs, but that they have subtle dependencies on each other: conflict resolution and aggregation change tool names, filtering changes which tools are available at different points in the pipeline, and downstream config blocks (rate limiting, composite tools) must reference tool names that earlier config blocks may have renamed or removed. The result is that configuring one feature correctly requires understanding the side effects of every other feature. A concrete example: the advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287) — the fact that the bug existed shows how opaque the interaction is. +### The configurability problem + +Users want to combine features in ways that make sense for their deployment. But each feature has its own config surface, and the interactions between them are implicit and surprising: + +- **Filter × composite tools**: The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287). RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is. +- **Rate limiting × overrides**: Rate limiting (THV-0057) adds per-tool limits that must reference tool names — names that may have been renamed by overrides in a different config block. +- **Optimizer × discoverability**: The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static. We've discussed many solutions on [this issue](https://github.com/stacklok/toolhive/issues/4357). To support them all, we'd have to add many more config knobs. As Alejandro's comment points out, we'd also like the solutions to not be one-size-fits-all — but each option is another knob. +- **Cross-cutting policies**: There is no mechanism to express policies that span features, like "tools without a `readOnly` annotation must require an elicitation step." + +The following diagram maps the dependencies between vMCP features ([excalidraw source](https://excalidraw.com/#json=C3Co-yHQMwzjrJptY7Qmv,mCqdzvMmerb6yZ0_gmt24g)): + +![vMCP feature dependency graph](./images/vmcp-feature-dependencies.png) + +Configuring one feature correctly requires understanding the side effects of every other feature — a burden that scales poorly. + +### The maintainability problem + +Every new capability must reason about every existing one. The config surfaces today: | Feature | Config surface | Introduced in | |---------|---------------|---------------| @@ -29,33 +46,36 @@ vMCP's feature set is growing. Each feature has arrived with its own configurati | Rate limiting | `rateLimiting.perUser`, `rateLimiting.global`, `rateLimiting.tools[]` | THV-0057 (proposed) | | Dynamic webhooks | `validating_webhooks[]`, `mutating_webhooks[]` | THV-0017 (proposed) | -Each knob is individually reasonable. The problem is their **interaction**. Today: - -- The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287) (RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is). -- Rate limiting (THV-0057) adds per-tool limits via yet another config block that must reference the same tool names that may have been renamed by overrides. -- There is no mechanism to express cross-cutting policies like "tools without a `readOnly` annotation must only be invokable via a composite tool that includes an elicitation step." -- The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static. We've discussed many solutions on [this issue](https://github.com/stacklok/toolhive/issues/4357). To support them all, we'd have to add many more config knobs. As Alejandro comment points out too, we'd also like the solutions to not be one-size fits all. There are valid reasons for allowing more configurability, but that comes at the cost of cognitive load for operators and maintainers. +Adding a simple capability (e.g., PII scrubbing) requires understanding how it interacts with filtering, renaming, the optimizer, composite tools, and rate limiting. Testing every combination is intractable. The interaction matrix grows quadratically — and so does the cost of getting it wrong. -Every new capability doubles the interaction matrix. The following diagram maps the dependencies between vMCP features ([excalidraw source](https://excalidraw.com/#json=C3Co-yHQMwzjrJptY7Qmv,mCqdzvMmerb6yZ0_gmt24g)): +### Why this is worth solving now -![vMCP feature dependency graph](./images/vmcp-feature-dependencies.png) +THV-0051 proposes Starlark for composite tools. Before that engine ships, we should decide whether Starlark is *only* for composite tools or whether it's the foundation for a unified session initialization model. Shipping THV-0051 as-is and then later expanding scope would mean a second migration. -Administrators who need non-trivial configurations must understand the ordering and interaction of all these knobs — a burden that scales poorly. +The cost equation favors acting now. As more capabilities land, the cost of retrofitting a composable system increases — more code to replace, more interactions to preserve. Meanwhile, the cost of building each new config knob is *also* increasing, because each knob must reason about its interactions with every existing knob. A session initialization model inverts this: new capabilities ship as simple built-in functions, and administrators compose them explicitly. The longer we wait, the more expensive both paths become. +## Goals -### Who is affected +- Define a Starlark-based programming model that subsumes tool advertising, renaming, optimizer behavior, and composite tool workflows into a single script that runs once per session +- Make it easy to add new capabilities (search indexing, PII scrubbing, rate limiting) as simple built-in functions rather than config knobs with complex interactions +- Preserve the existing authorization boundary — Cedar authz middleware continues to filter `tools/list` and gate `tools/call` at runtime, independent of what the script publishes +- Make the system accessible to non-power-users by preserving the configuration that we have today. +- Enable policies that span multiple features (e.g., "non-readonly tools require elicitation") +- Maintain full backward compatibility with existing config fields — the session initialization script must be able to replicate every behavior currently achievable via `aggregation`, `optimizer`, and related config (except legacy composite tools, which are replaced by Starlark scripts). The plan for legacy composite tools is discussed in the implementation plan below. -- **Platform administrators** who configure vMCP for multi-tenant deployments and need predictable behavior from feature combinations. -- **Enterprise integrators** who need custom policies (PII scrubbing, approval workflows, tool restrictions) but don't want to fork ToolHive or maintain webhook services for simple logic. -- **The vMCP maintainers** who must reason about the interaction of every new feature with every existing feature. +## Non-Goals -### Why this is worth solving now +- Replacing Cedar for authorization decisions — Cedar remains the policy engine for access control +- A general-purpose plugin system for ToolHive beyond vMCP session behavior +- Replacing dynamic webhooks (THV-0017) — webhooks serve the external integration use case; Starlark serves the internal configuration use case +- Moving authentication or transport-level concerns into Starlark +- Supporting multiple scripting languages -THV-0051 proposes Starlark for composite tools. Before that engine ships, we should decide whether Starlark is *only* for composite tools or whether it's the foundation for a unified session initialization model. Shipping THV-0051 as-is and then later expanding scope would mean a second migration. +## Proposed Solution -The cost equation also favors acting now. As more capabilities are added, the cost of retrofitting a composable system increases — more code to replace, more interactions to preserve. Meanwhile, the cost of building each new config knob is *also* increasing, because each knob must reason about its interactions with every existing knob and implement more than a simple built-in. A session initialization model inverts this: new capabilities ship as simple built-in functions, and administrators compose them as needed. The longer we wait, the more expensive both paths become. +### Background -### Prior Art: Gateway Configurability Patterns +#### Prior art: gateway configurability patterns Envoy Proxy faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal Lua filter, WASM filters, and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its ext_authz filter, with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. @@ -63,7 +83,7 @@ Kong Gateway built its plugin architecture on Lua lifecycle callbacks with a Plu The [Configuration Complexity Clock](https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html) (Hadlow, 2012) describes the lifecycle this RFC interrupts: hard-coded values → config file → complex config → rules engine → DSL → "essentially a programming language, except crappier." vMCP's config knob interactions are at the "complex config" stage. The session initialization model jumps to a real programming language with proper semantics, rather than waiting for the config surface to accumulate ad-hoc conditionals that amount to a worse one. -### Background: How authorization works today +#### How authorization works today Authorization in vMCP is enforced by authz middleware that sits between the client and the session: @@ -80,25 +100,6 @@ Critically, the authz middleware operates on the *final published tool set* — Modifying the authz boundary is out of scope for this RFC. -## Goals - -- Define a Starlark-based programming model that subsumes tool advertising, renaming, optimizer behavior, and composite tool workflows into a single script that runs once per session -- Make it easy to add new capabilities (search indexing, PII scrubbing, rate limiting) as simple built-in functions rather than config knobs with complex interactions -- Preserve the existing authorization boundary — Cedar authz middleware continues to filter `tools/list` and gate `tools/call` at runtime, independent of what the script publishes -- Make the system accessible to non-power-users by preserving the configuration that we have today. -- Enable policies that span multiple features (e.g., "non-readonly tools require elicitation") -- Maintain full backward compatibility with existing config fields — the session initialization script must be able to replicate every behavior currently achievable via `aggregation`, `optimizer`, and related config (except legacy composite tools, which are replaced by Starlark scripts). The plan for legacy composite tools is discussed in the implementation plan below. - -## Non-Goals - -- Replacing Cedar for authorization decisions — Cedar remains the policy engine for access control -- A general-purpose plugin system for ToolHive beyond vMCP session behavior -- Replacing dynamic webhooks (THV-0017) — webhooks serve the external integration use case; Starlark serves the internal configuration use case -- Moving authentication or transport-level concerns into Starlark -- Supporting multiple scripting languages - -## Proposed Solution - ### High-Level Design A vMCP caller runs a single Starlark **session initialization script** once per session. The script receives discovered backends via `backends()` — a dict keyed by backend name, where each value exposes the backend's tools, resources, and prompts. The script calls `publish()` to declare what the agent sees. @@ -688,7 +689,7 @@ The session initialization script is not a decorator — it is used during sessi #### Interaction with authorization -As described in [Background: How authorization works today](#background-how-authorization-works-today), the session initialization script runs during session construction — before the authz middleware. `backends()` returns all backends configured, regardless of the current user's authorization. +As described in [How authorization works today](#how-authorization-works-today), the session initialization script runs during session construction — before the authz middleware. `backends()` returns all backends configured, regardless of the current user's authorization. The authz middleware continues to enforce authorization at runtime: From f91119187bbb7a5aa13df05da23c83f8164c8080 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 17:51:27 -0700 Subject: [PATCH 11/19] Sharpen Problem Statement and add links to prior art Reframe problem as configurability vs maintainability tension. Move bug evidence and dependency diagram to maintainability section. Remove feature table (duplicative with diagram). Update summary to mirror problem framing. Add links to Envoy, Kong, and Configuration Complexity Clock resources. Co-Authored-By: Claude Opus 4.6 --- ...V-0060-starlark-programmable-middleware.md | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index 9f5a209..530ee0f 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -10,43 +10,31 @@ ## Summary -Introduce a Starlark-based session initialization script for vMCP. A single script runs once per session, receives discovered backends and their capabilities, and calls `publish()` to declare what the agent sees — optionally wrapping handlers with additional logic. Existing config knobs remain fully supported, but customization of vMCP behavior can now be exactly tailored to the use case without adding more knobs and the cognitive load of their interactions. +Introduce a Starlark-based session initialization script for vMCP. A single script runs once per session, receives discovered backends and their capabilities, and calls `publish()` to declare what the agent sees — optionally wrapping handlers with additional logic. Existing config knobs remain fully supported, but customization of vMCP behavior can now be exactly tailored to the use case without adding more knobs. Increasing configurability no longer means decreasing maintainability — new capabilities ship as simple built-in functions instead of config knobs that must reason about every other knob. ## Problem Statement -vMCP's feature set is growing, and two forces are pulling against each other: users want more configurability, maintainers need the system to stay understandable. Each new capability makes both sides worse. +vMCP's feature set is growing, and two forces are pulling against each other: users want more configurability, but increasing configurability decreases maintainability. ### The configurability problem -Users want to combine features in ways that make sense for their deployment. But each feature has its own config surface, and the interactions between them are implicit and surprising: +Users want to combine features in ways that make sense for their deployment. But each feature has its own config surface, and the interactions between them are implicit and surprising. Configuring one feature correctly requires understanding the side effects of every other feature: -- **Filter × composite tools**: The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287). RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is. -- **Rate limiting × overrides**: Rate limiting (THV-0057) adds per-tool limits that must reference tool names — names that may have been renamed by overrides in a different config block. -- **Optimizer × discoverability**: The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static. We've discussed many solutions on [this issue](https://github.com/stacklok/toolhive/issues/4357). To support them all, we'd have to add many more config knobs. As Alejandro's comment points out, we'd also like the solutions to not be one-size-fits-all — but each option is another knob. -- **Cross-cutting policies**: There is no mechanism to express policies that span features, like "tools without a `readOnly` annotation must require an elicitation step." - -The following diagram maps the dependencies between vMCP features ([excalidraw source](https://excalidraw.com/#json=C3Co-yHQMwzjrJptY7Qmv,mCqdzvMmerb6yZ0_gmt24g)): +- The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static. We've discussed many solutions on [this issue](https://github.com/stacklok/toolhive/issues/4357). To support them all, we'd have to add many more config knobs. As Alejandro's comment points out, we'd also like the solutions to not be one-size-fits-all — but each option is another knob. +- Rate limiting (THV-0057) adds per-tool limits that must reference tool names — names that may have been renamed by overrides in a different config block. +- There is no mechanism to express policies that span features, like "tools without a `readOnly` annotation must require an elicitation step." -![vMCP feature dependency graph](./images/vmcp-feature-dependencies.png) +### The maintainability problem -Configuring one feature correctly requires understanding the side effects of every other feature — a burden that scales poorly. +Every new feature enters a web of dependencies with existing features. As we add to this web, we have to think carefully about how each addition interacts with everything else ([excalidraw source](https://excalidraw.com/#json=C3Co-yHQMwzjrJptY7Qmv,mCqdzvMmerb6yZ0_gmt24g)): -### The maintainability problem +![vMCP feature dependency graph](./images/vmcp-feature-dependencies.png) -Every new capability must reason about every existing one. The config surfaces today: +The cost is concrete — implicit interactions produce bugs: -| Feature | Config surface | Introduced in | -|---------|---------------|---------------| -| Tool advertising filter | `aggregation.tools[].filter`, `excludeAll` | THV-0008 | -| Tool renaming / overrides | `aggregation.tools[].overrides` | THV-0008 | -| Conflict resolution | `aggregation.conflictResolution` | THV-0008 | -| Composite tools | `compositeTools[]`, `compositeToolRefs[]` | THV-0008 | -| Optimizer | `optimizer` (embedding service URL, thresholds, max results) | THV-0022 | -| Starlark scripted tools | `scriptedTools[]`, `scriptedToolRefs[]` | THV-0051 (proposed) | -| Rate limiting | `rateLimiting.perUser`, `rateLimiting.global`, `rateLimiting.tools[]` | THV-0057 (proposed) | -| Dynamic webhooks | `validating_webhooks[]`, `mutating_webhooks[]` | THV-0017 (proposed) | +- **Filter × composite tools**: The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287). RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is. -Adding a simple capability (e.g., PII scrubbing) requires understanding how it interacts with filtering, renaming, the optimizer, composite tools, and rate limiting. Testing every combination is intractable. The interaction matrix grows quadratically — and so does the cost of getting it wrong. +Adding a simple capability (e.g., PII scrubbing) requires understanding how it interacts with filtering, renaming, the optimizer, composite tools, and rate limiting. Testing every combination is intractable. The interaction matrix grows quadratically — and so does the time to ship new features and the cost of getting it wrong. ### Why this is worth solving now @@ -77,11 +65,11 @@ The cost equation favors acting now. As more capabilities land, the cost of retr #### Prior art: gateway configurability patterns -Envoy Proxy faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal Lua filter, WASM filters, and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its ext_authz filter, with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. +**[Envoy Proxy](https://www.envoyproxy.io/)** faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal [Lua filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter), [WASM filters](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter), and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its [ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter), with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. -Kong Gateway built its plugin architecture on Lua lifecycle callbacks with a Plugin Development Kit exposing built-in functions for request inspection and response control. The pattern — built-in functions as mechanism, user scripts as policy — directly informs vMCP's `publish()` + handler model. +**[Kong Gateway](https://docs.konghq.com/gateway/latest/)** built its plugin architecture on Lua lifecycle callbacks with a [Plugin Development Kit](https://docs.konghq.com/gateway/latest/plugin-development/pdk/) exposing built-in functions for request inspection and response control. The pattern — built-in functions as mechanism, user scripts as policy — directly informs vMCP's `publish()` + handler model. -The [Configuration Complexity Clock](https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html) (Hadlow, 2012) describes the lifecycle this RFC interrupts: hard-coded values → config file → complex config → rules engine → DSL → "essentially a programming language, except crappier." vMCP's config knob interactions are at the "complex config" stage. The session initialization model jumps to a real programming language with proper semantics, rather than waiting for the config surface to accumulate ad-hoc conditionals that amount to a worse one. +**[The Configuration Complexity Clock](https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html)** (Hadlow, 2012) describes the lifecycle this RFC interrupts: hard-coded values → config file → complex config → rules engine → DSL → "essentially a programming language, except crappier." vMCP's config knob interactions are at the "complex config" stage. The session initialization model jumps to a real programming language with proper semantics, rather than waiting for the config surface to accumulate ad-hoc conditionals that amount to a worse one. #### How authorization works today From 62111118f8ba5a6f4611024a96cf4b571ff612f3 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 18:42:09 -0700 Subject: [PATCH 12/19] Strengthen Problem Statement and add code snippets to prior art MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elaborate rate limiting interaction questions (composite tools, groups). Add optimizer × authz bugs (#4373, #4374) to maintainability section. Replace PII hypothetical with concrete framing. Add Envoy and Kong source code snippets with GitHub links to prior art section. Co-Authored-By: Claude Opus 4.6 --- ...V-0060-starlark-programmable-middleware.md | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index 530ee0f..c8e7ebf 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -21,7 +21,7 @@ vMCP's feature set is growing, and two forces are pulling against each other: us Users want to combine features in ways that make sense for their deployment. But each feature has its own config surface, and the interactions between them are implicit and surprising. Configuring one feature correctly requires understanding the side effects of every other feature: - The optimizer replaces the entire tool list with `find_tool` / `call_tool`, but `find_tool`'s description is static. We've discussed many solutions on [this issue](https://github.com/stacklok/toolhive/issues/4357). To support them all, we'd have to add many more config knobs. As Alejandro's comment points out, we'd also like the solutions to not be one-size-fits-all — but each option is another knob. -- Rate limiting (THV-0057) adds per-tool limits that must reference tool names — names that may have been renamed by overrides in a different config block. +- Rate limiting (THV-0057) adds per-tool limits that must reference tool names — but those names may have been renamed by overrides in a different config block. Users also have to know that limits apply to post-resolution names. What happens when a rate-limited tool is called inside a composite tool, potentially many times? Is it still rate limited? What if an administrator wants different limits for groups of tools or entire backends? Each question either becomes another knob or hard-coded behavior that needs documentation — and users reading that documentation. - There is no mechanism to express policies that span features, like "tools without a `readOnly` annotation must require an elicitation step." ### The maintainability problem @@ -33,8 +33,9 @@ Every new feature enters a web of dependencies with existing features. As we add The cost is concrete — implicit interactions produce bugs: - **Filter × composite tools**: The advertising filter runs before composite tools, causing a [type coercion bug](https://github.com/stacklok/toolhive/issues/4287). RFC-0058 fixes the ordering, but the fact that the bug existed shows how opaque the interaction is. +- **Optimizer × authorization**: The optimizer wasn't tested with Cedar authz due to time constraints. The result: enabling the optimizer silently breaks Cedar policies that reference real tool names ([#4373](https://github.com/stacklok/toolhive/issues/4373)), and `find_tool` returns tools the caller isn't authorized to use ([#4374](https://github.com/stacklok/toolhive/issues/4374)). -Adding a simple capability (e.g., PII scrubbing) requires understanding how it interacts with filtering, renaming, the optimizer, composite tools, and rate limiting. Testing every combination is intractable. The interaction matrix grows quadratically — and so does the time to ship new features and the cost of getting it wrong. +In practice, it's infeasible to always test all feature combinations and remain aware of their interactions. The interaction matrix grows quadratically — and so does the time to ship new features and the cost of getting it wrong. Instead, the relationships between features should be flexible but explicit. ### Why this is worth solving now @@ -65,9 +66,30 @@ The cost equation favors acting now. As more capabilities land, the cost of retr #### Prior art: gateway configurability patterns -**[Envoy Proxy](https://www.envoyproxy.io/)** faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal [Lua filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter), [WASM filters](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter), and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its [ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter), with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. +**[Envoy Proxy](https://www.envoyproxy.io/)** faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal [Lua filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter), [WASM filters](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter), and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its [ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter), with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. Envoy's Lua filter exposes built-in functions for request/response manipulation ([source](https://github.com/envoyproxy/envoy/blob/main/source/extensions/filters/http/lua/lua_filter.cc)): -**[Kong Gateway](https://docs.konghq.com/gateway/latest/)** built its plugin architecture on Lua lifecycle callbacks with a [Plugin Development Kit](https://docs.konghq.com/gateway/latest/plugin-development/pdk/) exposing built-in functions for request inspection and response control. The pattern — built-in functions as mechanism, user scripts as policy — directly informs vMCP's `publish()` + handler model. +```lua +-- Envoy Lua filter: built-in functions as escape hatch from declarative config +function envoy_on_request(request_handle) + local headers = request_handle:headers() + if headers:get("x-custom-header") == nil then + request_handle:respond({[":status"] = "403"}, "Forbidden") + end +end +``` + +**[Kong Gateway](https://docs.konghq.com/gateway/latest/)** built its plugin architecture on Lua lifecycle callbacks with a [Plugin Development Kit](https://docs.konghq.com/gateway/latest/plugin-development/pdk/) exposing built-in functions for request inspection and response control. The pattern — built-in functions as mechanism, user scripts as policy — directly informs vMCP's `publish()` + handler model ([source](https://github.com/Kong/kong/blob/master/kong/pdk/init.lua)): + +```lua +-- Kong plugin: lifecycle callbacks + PDK built-ins +function MyPlugin:access(conf) + local consumer = kong.client.get_consumer() + if not consumer then + return kong.response.exit(403, { message = "Unauthorized" }) + end + kong.service.request.set_header("X-Consumer-ID", consumer.id) +end +``` **[The Configuration Complexity Clock](https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html)** (Hadlow, 2012) describes the lifecycle this RFC interrupts: hard-coded values → config file → complex config → rules engine → DSL → "essentially a programming language, except crappier." vMCP's config knob interactions are at the "complex config" stage. The session initialization model jumps to a real programming language with proper semantics, rather than waiting for the config surface to accumulate ad-hoc conditionals that amount to a worse one. From d6e907baf7541b48ec95a97f71e5d4572917b80a Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 18:56:46 -0700 Subject: [PATCH 13/19] Use verbatim doc examples for Envoy and Kong prior art Replace hand-written code snippets with examples from official documentation. Link to doc pages instead of source files. Co-Authored-By: Claude Opus 4.6 --- ...V-0060-starlark-programmable-middleware.md | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index c8e7ebf..e01cfab 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -66,29 +66,42 @@ The cost equation favors acting now. As more capabilities land, the cost of retr #### Prior art: gateway configurability patterns -**[Envoy Proxy](https://www.envoyproxy.io/)** faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal [Lua filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter), [WASM filters](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter), and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its [ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter), with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. Envoy's Lua filter exposes built-in functions for request/response manipulation ([source](https://github.com/envoyproxy/envoy/blob/main/source/extensions/filters/http/lua/lua_filter.cc)): +**[Envoy Proxy](https://www.envoyproxy.io/)** faces the same configurability spectrum. Its declarative config handles routing well, but complex use cases require escape hatches: a minimal [Lua filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter), [WASM filters](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter), and native C++ filters, each trading simplicity for power. Envoy keeps authorization architecturally separate from routing via its [ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter), with shared context flowing between them through dynamic metadata — authorization and configuration are separate concerns connected through a shared namespace, not unified into one layer. Envoy's Lua filter exposes built-in functions for request/response manipulation ([docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter)): ```lua --- Envoy Lua filter: built-in functions as escape hatch from declarative config +-- Envoy Lua filter (from docs) function envoy_on_request(request_handle) - local headers = request_handle:headers() - if headers:get("x-custom-header") == nil then - request_handle:respond({[":status"] = "403"}, "Forbidden") - end + request_handle:headers():add("request_body_size", request_handle:body():length()) +end + +function envoy_on_response(response_handle) + response_handle:headers():add("response_body_size", response_handle:body():length()) + response_handle:headers():remove("foo") end ``` -**[Kong Gateway](https://docs.konghq.com/gateway/latest/)** built its plugin architecture on Lua lifecycle callbacks with a [Plugin Development Kit](https://docs.konghq.com/gateway/latest/plugin-development/pdk/) exposing built-in functions for request inspection and response control. The pattern — built-in functions as mechanism, user scripts as policy — directly informs vMCP's `publish()` + handler model ([source](https://github.com/Kong/kong/blob/master/kong/pdk/init.lua)): +**[Kong Gateway](https://docs.konghq.com/gateway/latest/)** built its plugin architecture on Lua lifecycle callbacks with a [Plugin Development Kit](https://docs.konghq.com/gateway/latest/plugin-development/pdk/) exposing built-in functions for request inspection and response control. The pattern — built-in functions as mechanism, user scripts as policy — directly informs vMCP's `publish()` + handler model ([docs](https://developer.konghq.com/custom-plugins/handler.lua/)): ```lua --- Kong plugin: lifecycle callbacks + PDK built-ins -function MyPlugin:access(conf) - local consumer = kong.client.get_consumer() - if not consumer then - return kong.response.exit(403, { message = "Unauthorized" }) - end - kong.service.request.set_header("X-Consumer-ID", consumer.id) +-- Kong plugin handler (from docs) +local CustomHandler = { + VERSION = "1.0.0", + PRIORITY = 10, +} + +function CustomHandler:access(config) + kong.log("access") end + +function CustomHandler:header_filter(config) + kong.log("header_filter") +end + +function CustomHandler:body_filter(config) + kong.log("body_filter") +end + +return CustomHandler ``` **[The Configuration Complexity Clock](https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html)** (Hadlow, 2012) describes the lifecycle this RFC interrupts: hard-coded values → config file → complex config → rules engine → DSL → "essentially a programming language, except crappier." vMCP's config knob interactions are at the "complex config" stage. The session initialization model jumps to a real programming language with proper semantics, rather than waiting for the config surface to accumulate ad-hoc conditionals that amount to a worse one. From fdca35af4627485925e732f651e4a68a72ea8f53 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 19:10:06 -0700 Subject: [PATCH 14/19] Move use cases to appendix and fix review items - Move Motivating Use Cases to Appendix A - Update Last Updated date to 2026-03-27 - Replace scrub_pii decorator example with v0 logging decorator - Restore missing filter decorator in architecture diagram - Remove duplicate composite tools mentions from presets/config sections - Note composite tools support is optional in compatibility section - Fix Open Question 3 bold formatting Co-Authored-By: Claude Opus 4.6 --- ...V-0060-starlark-programmable-middleware.md | 526 +++++++++--------- 1 file changed, 263 insertions(+), 263 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index e01cfab..f958b0f 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -3,7 +3,7 @@ - **Status**: Draft - **Author(s)**: Jeremy Drouillard (@jerm-dro) - **Created**: 2026-03-24 -- **Last Updated**: 2026-03-25 +- **Last Updated**: 2026-03-27 - **Target Repository**: toolhive - **Related Issues**: [stacklok-epics#213](https://github.com/stacklok/stacklok-epics/issues/213) - **Related**: [THV-0051 (Starlark Scripted Tools)](./THV-0051-starlark-scripted-tools.md) — this RFC broadens the scope of Starlark in vMCP from composite tool workflows to a unified session initialization model @@ -206,18 +206,18 @@ for name, backend in backends().items(): Since handlers are just functions, decoration is plain function wrapping: ```python -def with_pii_scrubbing(fn): - """Wrap a handler to scrub PII from responses.""" +def with_logging(fn, tool_name): + """Wrap a handler to log calls.""" def wrapper(args): + log("calling %s" % tool_name) result = fn(args) - if "text" in result: - result["text"] = scrub_pii(result["text"]) + log("finished %s" % tool_name) return result return wrapper for name, backend in backends().items(): for meta, fn in backend.tools(): - publish(meta, with_pii_scrubbing(fn)) + publish(meta, with_logging(fn, meta.name)) ``` @@ -237,256 +237,7 @@ publish( ) ``` -### Motivating Use Cases - -The following use cases illustrate what the programming model enables. Use Cases 1, 2, and 4 are achievable with the v0 built-ins. Use Cases 3, 5, and 6 depend on future built-ins (`current_user()`, `scrub_pii()`, `check_rate_limit()`) that are not in scope for this RFC but demonstrate why the model is worth building. - -#### Use Case 1: Dynamic optimizer descriptions - -**Problem**: Agents don't use `find_tool` because its static description doesn't tell them what's available. - -**Today's solution**: Manual description override or hope the agent figures it out. - -**With session initialization script**: - -```python -all_tools = [] -for name, backend in backends().items(): - all_tools += backend.tools() - -index = search_index(all_tools) - -# Build a dynamic description from actual available backends -desc_parts = [] -for name, backend in backends().items(): - n = len(backend.tools()) - desc_parts.append("%s (%d tools)" % (name, n)) - -summary = "Search for tools. Available: " + ", ".join(desc_parts) - -publish( - metadata(name="find_tool", description=summary, - parameters=FIND_TOOL_SCHEMA, annotations={}), - lambda args: {"results": index.search(args["query"])}, -) - -# Save handlers by name for dispatch -tool_handlers = {} -for name, backend in backends().items(): - for meta, fn in backend.tools(): - tool_handlers[meta.name] = fn - -publish( - metadata(name="call_tool", description="Call a tool by name.", - parameters=CALL_TOOL_SCHEMA, annotations={}), - lambda args: tool_handlers[args["tool_name"]](args["arguments"]), -) -``` - -The value here is that the administrator defines the policy that works best for their use case. ToolHive doesn't need to build one-size-fits-all solutions for optimizer behavior — the script is the policy. - -#### Use Case 2: Elicitation gate for write operations - -**Problem**: An administrator wants to ensure that tools capable of mutation are never called without human confirmation. - -**Today's solution**: Not possible without writing a custom composite tool wrapper for every write tool. - -**With session initialization script**: - -```python -def with_approval_gate(fn, tool_name): - def wrapper(args): - decision = elicit( - "Tool '%s' may modify data. Approve?" % tool_name, - schema={"type": "object", "properties": {"reason": {"type": "string"}}}, - ) - if decision.action != "accept": - return {"error": "Declined by user"} - return fn(args) - return wrapper - -for name, backend in backends().items(): - for meta, fn in backend.tools(): - if not meta.annotations.get("readOnly", False): - fn = with_approval_gate(fn, meta.name) - publish(meta, fn) -``` - -A single policy, applied once, covering all tools. - -#### Use Case 3: PII scrubbing - -**Problem**: Tool responses may contain PII that should be redacted before reaching the agent. - -**Today's solution**: Requires a mutating webhook (THV-0017) calling an external service. - -**With session initialization script**: - -```python -def with_pii_scrubbing(fn): - def wrapper(args): - result = fn(args) - if "text" in result: - result["text"] = scrub_pii(result["text"]) - return result - return wrapper - -for name, backend in backends().items(): - for meta, fn in backend.tools(): - publish(meta, with_pii_scrubbing(fn)) -``` - -`scrub_pii()` is an example of a future built-in (not in scope for this RFC) that could handle common patterns (emails, phone numbers, SSNs, credit cards). Users can also write their own scrubbing decorators — the programming model makes this natural without needing ToolHive to explicitly support every scrubbing pattern. - -#### Use Case 4: Tool aggregation and renaming - -**Problem**: An administrator wants to present a curated set of tools — renaming some, hiding others, grouping related tools under a single facade. - -**Today's solution**: `aggregation.tools[].overrides` for renaming, `aggregation.tools[].filter` / `excludeAll` for hiding. - -**With session initialization script**: - -```python -jira_handlers = {} - -for name, backend in backends().items(): - for meta, fn in backend.tools(): - # Hide internal tools - if meta.name.startswith("internal_"): - continue - - # Rename for clarity - if meta.name == "pg_query": - publish( - metadata(name="database_query", description="Query the production database", - parameters=meta.parameters, annotations=meta.annotations), - fn, - ) - continue - - # Save Jira tools — we'll group them below - if meta.name in ["jira_create", "jira_update", "jira_search"]: - jira_handlers[meta.name] = fn - continue - - publish(meta, fn) - -# Publish a composite Jira tool using saved handlers -def jira_handler(args): - action = args["action"] - return jira_handlers["jira_" + action](args) - -publish( - metadata(name="jira", description="Manage Jira issues: create, update, or search", - parameters=JIRA_SCHEMA, annotations={}), - jira_handler, -) -``` - -#### Use Case 5: Rate limiting with context-aware policies - -**Problem**: Rate limits need to vary by tool sensitivity and user role. - -**Today's solution**: THV-0057 provides static `requestsPerWindow` / `windowSeconds` per tool. - -**With session initialization script**: - -```python -LIMITS = { - "admin": {"default": 1000, "expensive_search": 100}, - "standard": {"default": 100, "expensive_search": 10}, -} - -def with_rate_limit(fn, tool_name): - def wrapper(args): - user = current_user() - role = user.groups[0] if user.groups else "standard" - role_limits = LIMITS.get(role, LIMITS["standard"]) - limit = role_limits.get(tool_name, role_limits["default"]) - - allowed, retry_after = check_rate_limit( - key=user.sub + ":" + tool_name, limit=limit, window=60, - ) - if not allowed: - return {"error": "Rate limited", "retry_after": retry_after} - return fn(args) - return wrapper - -for name, backend in backends().items(): - for meta, fn in backend.tools(): - publish(meta, with_rate_limit(fn, meta.name)) -``` - -`check_rate_limit()` is backed by the same Redis token bucket from THV-0057. The *policy* is expressed in Starlark; the *mechanism* lives in Go. This allows users to define rate limiting that meets the needs of their use case without ToolHive needing to explicitly support every rate limiting pattern. - -#### Use Case 6: Composing multiple concerns - -A single script handles optimizer + elicitation gate + PII scrubbing + rate limiting — behaviors that today require four different config surfaces: - -```python -def build_summary(tool_list): - cats = {} - for meta, fn in tool_list: - cat = meta.annotations.get("category", "general") - if cat not in cats: - cats[cat] = [] - cats[cat].append(meta.name) - return "Search for tools across: " + ", ".join( - "%s (%d tools)" % (c, len(ns)) for c, ns in cats.items() - ) - -all_tools = [] -tool_handlers = {} -tool_metadata = {} - -for name, backend in backends().items(): - for meta, fn in backend.tools(): - all_tools.append((meta, fn)) - tool_handlers[meta.name] = fn - tool_metadata[meta.name] = meta - -index = search_index(all_tools) -desc = build_summary(all_tools) - -def dispatch(args): - tool_name = args["tool_name"] - arguments = args["arguments"] - user = current_user() - - # Rate limit - allowed, retry_after = check_rate_limit( - key=user.sub + ":" + tool_name, limit=100, window=60, - ) - if not allowed: - return {"error": "Rate limited", "retry_after": retry_after} - - # Elicitation gate for non-readonly tools - meta = tool_metadata.get(tool_name) - if meta and not meta.annotations.get("readOnly", False): - decision = elicit("Approve call to '%s'?" % tool_name) - if decision.action != "accept": - return {"error": "Declined"} - - # Execute and scrub - result = tool_handlers[tool_name](arguments) - if "text" in result: - result["text"] = scrub_pii(result["text"]) - return result - -publish( - metadata(name="find_tool", description=desc, - parameters=FIND_TOOL_SCHEMA, annotations={}), - lambda args: {"results": index.search(args["query"])}, -) - -publish( - metadata(name="call_tool", description="Call a tool by name.", - parameters=CALL_TOOL_SCHEMA, annotations={}), - dispatch, -) -``` - -The ordering is explicit. The interactions are visible. There are no surprising feature interactions because the administrator wrote the interaction. +For detailed use case examples showing these patterns in practice, see [Appendix A: Motivating Use Cases](#appendix-a-motivating-use-cases). ### Built-in Functions @@ -553,8 +304,6 @@ This prints the Starlark source. A user who needs 90% of a preset's behavior can A single `default` preset handles all existing config knobs. When no `sessionInit` block is present, vMCP uses the `default` preset, which reads the existing config fields and produces identical behavior. There is no separate legacy code path — the Starlark engine is the single implementation. -The only exception is legacy declarative composite tools (`compositeTools`, `compositeToolRefs`), which are not supported in the session initialization script. These are replaced by Starlark scripted tools from THV-0051. - #### Sketch of the `default` preset The `default` preset is the most complex part of this RFC — it must faithfully replicate the behavior of the existing config-driven system. Below is a sketch of what this script looks like. The exact implementation will be validated during the POC phase. @@ -703,7 +452,7 @@ The session initialization script replaces the current decorator stack for tool- Current model: New model: optimizer decorator Session factory runs - session init script, + filter decorator session init script, composite tools decorator constructs MultiSession base session from publish() results ``` @@ -762,8 +511,6 @@ type SessionInitConfig struct { `aggregation`, `compositeToolRefs`, and `optimizer` remain on `Config`. The `default` preset reads these fields and produces identical behavior. When a custom `sessionInit.script` or `sessionInit.scriptFile` is set, these fields are ignored (if both are set, vMCP logs a warning). -Legacy declarative composite tools (`compositeTools`, `compositeToolRefs`) are not supported in the session initialization script and will be removed in a future release. - ## Security Considerations @@ -893,7 +640,7 @@ Instead of a scripting language, make the config ordering explicit — a pipelin All existing config fields (`aggregation`, `optimizer`) continue to produce identical behavior. The `default` preset reads these fields and produces the same behavior as the current config-driven system. There is no separate legacy code path. -The exception is legacy declarative composite tools (`compositeTools`, `compositeToolRefs`), which are replaced by Starlark scripted tools from THV-0051. +Legacy declarative composite tools (`compositeTools`, `compositeToolRefs`) could be supported by having the `default` preset interpret them, but this is not required. If we want to cut scope, composite tools can be deprecated directly and users migrated to Starlark scripts (THV-0051). ### Forward Compatibility @@ -958,7 +705,7 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc 2. **Should authz decisions move into Starlark?** Authorization (Cedar) and session initialization (Starlark) remain entirely separate systems. This RFC reduces config knob interactions significantly and makes most of them explicit, but the "who sees what?" question still requires reasoning across both systems. The interaction between the optimizer and Cedar illustrates the problem: enabling the optimizer replaces real tool names with `find_tool` / `call_tool`, which silently breaks Cedar policies that reference the original names ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373))); and `find_tool` returns tools the caller isn't authorized to use, because Cedar gates `tools/call` but doesn't filter search results inside a handler ([stacklok/toolhive#4374](https://github.com/stacklok/toolhive/issues/4374)). Neither system is aware of the other. Pulling authz decisions into the script (e.g., a `current_user()` built-in combined with policy logic) would unify the model but raises questions about Cedar's role and the trust boundary. Worth exploring once the base programming model is proven. -3. What happens when MCP supports requests without sessions? Do we have to run this heavy script on every request? We could actually run the script once at startup, since it does not depend on request-time information. However, if we fold in authz concerns from above, then `current_user()` will be request-time information. We could cheat around this by recommending all logic which depends on `current_user()` be placed at the end of the script. When that's encountered during startup, we block and restore the state on each request. Alternatively, we could support two different scripts. One for initialization and one per-request. +3. **Sessionless MCP requests**: What happens when MCP supports requests without sessions? Do we have to run this heavy script on every request? We could actually run the script once at startup, since it does not depend on request-time information. However, if we fold in authz concerns from above, then `current_user()` will be request-time information. We could cheat around this by recommending all logic which depends on `current_user()` be placed at the end of the script. When that's encountered during startup, we block and restore the state on each request. Alternatively, we could support two different scripts. One for initialization and one per-request. ## References @@ -972,6 +719,259 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc --- +## Appendix A: Motivating Use Cases + +The following use cases illustrate what the programming model enables. Use Cases 1, 2, and 4 are achievable with the v0 built-ins. Use Cases 3, 5, and 6 depend on future built-ins (`current_user()`, `scrub_pii()`, `check_rate_limit()`) that are not in scope for this RFC but demonstrate why the model is worth building. + +### Use Case 1: Dynamic optimizer descriptions + +**Problem**: Agents don't use `find_tool` because its static description doesn't tell them what's available. + +**Today's solution**: Manual description override or hope the agent figures it out. + +**With session initialization script**: + +```python +all_tools = [] +for name, backend in backends().items(): + all_tools += backend.tools() + +index = search_index(all_tools) + +# Build a dynamic description from actual available backends +desc_parts = [] +for name, backend in backends().items(): + n = len(backend.tools()) + desc_parts.append("%s (%d tools)" % (name, n)) + +summary = "Search for tools. Available: " + ", ".join(desc_parts) + +publish( + metadata(name="find_tool", description=summary, + parameters=FIND_TOOL_SCHEMA, annotations={}), + lambda args: {"results": index.search(args["query"])}, +) + +# Save handlers by name for dispatch +tool_handlers = {} +for name, backend in backends().items(): + for meta, fn in backend.tools(): + tool_handlers[meta.name] = fn + +publish( + metadata(name="call_tool", description="Call a tool by name.", + parameters=CALL_TOOL_SCHEMA, annotations={}), + lambda args: tool_handlers[args["tool_name"]](args["arguments"]), +) +``` + +The value here is that the administrator defines the policy that works best for their use case. ToolHive doesn't need to build one-size-fits-all solutions for optimizer behavior — the script is the policy. + +### Use Case 2: Elicitation gate for write operations + +**Problem**: An administrator wants to ensure that tools capable of mutation are never called without human confirmation. + +**Today's solution**: Not possible without writing a custom composite tool wrapper for every write tool. + +**With session initialization script**: + +```python +def with_approval_gate(fn, tool_name): + def wrapper(args): + decision = elicit( + "Tool '%s' may modify data. Approve?" % tool_name, + schema={"type": "object", "properties": {"reason": {"type": "string"}}}, + ) + if decision.action != "accept": + return {"error": "Declined by user"} + return fn(args) + return wrapper + +for name, backend in backends().items(): + for meta, fn in backend.tools(): + if not meta.annotations.get("readOnly", False): + fn = with_approval_gate(fn, meta.name) + publish(meta, fn) +``` + +A single policy, applied once, covering all tools. + +### Use Case 3: PII scrubbing + +**Problem**: Tool responses may contain PII that should be redacted before reaching the agent. + +**Today's solution**: Requires a mutating webhook (THV-0017) calling an external service. + +**With session initialization script**: + +```python +def with_pii_scrubbing(fn): + def wrapper(args): + result = fn(args) + if "text" in result: + result["text"] = scrub_pii(result["text"]) + return result + return wrapper + +for name, backend in backends().items(): + for meta, fn in backend.tools(): + publish(meta, with_pii_scrubbing(fn)) +``` + +`scrub_pii()` is an example of a future built-in (not in scope for this RFC) that could handle common patterns (emails, phone numbers, SSNs, credit cards). Users can also write their own scrubbing decorators — the programming model makes this natural without needing ToolHive to explicitly support every scrubbing pattern. + +### Use Case 4: Tool aggregation and renaming + +**Problem**: An administrator wants to present a curated set of tools — renaming some, hiding others, grouping related tools under a single facade. + +**Today's solution**: `aggregation.tools[].overrides` for renaming, `aggregation.tools[].filter` / `excludeAll` for hiding. + +**With session initialization script**: + +```python +jira_handlers = {} + +for name, backend in backends().items(): + for meta, fn in backend.tools(): + # Hide internal tools + if meta.name.startswith("internal_"): + continue + + # Rename for clarity + if meta.name == "pg_query": + publish( + metadata(name="database_query", description="Query the production database", + parameters=meta.parameters, annotations=meta.annotations), + fn, + ) + continue + + # Save Jira tools — we'll group them below + if meta.name in ["jira_create", "jira_update", "jira_search"]: + jira_handlers[meta.name] = fn + continue + + publish(meta, fn) + +# Publish a composite Jira tool using saved handlers +def jira_handler(args): + action = args["action"] + return jira_handlers["jira_" + action](args) + +publish( + metadata(name="jira", description="Manage Jira issues: create, update, or search", + parameters=JIRA_SCHEMA, annotations={}), + jira_handler, +) +``` + +### Use Case 5: Rate limiting with context-aware policies + +**Problem**: Rate limits need to vary by tool sensitivity and user role. + +**Today's solution**: THV-0057 provides static `requestsPerWindow` / `windowSeconds` per tool. + +**With session initialization script**: + +```python +LIMITS = { + "admin": {"default": 1000, "expensive_search": 100}, + "standard": {"default": 100, "expensive_search": 10}, +} + +def with_rate_limit(fn, tool_name): + def wrapper(args): + user = current_user() + role = user.groups[0] if user.groups else "standard" + role_limits = LIMITS.get(role, LIMITS["standard"]) + limit = role_limits.get(tool_name, role_limits["default"]) + + allowed, retry_after = check_rate_limit( + key=user.sub + ":" + tool_name, limit=limit, window=60, + ) + if not allowed: + return {"error": "Rate limited", "retry_after": retry_after} + return fn(args) + return wrapper + +for name, backend in backends().items(): + for meta, fn in backend.tools(): + publish(meta, with_rate_limit(fn, meta.name)) +``` + +`check_rate_limit()` is backed by the same Redis token bucket from THV-0057. The *policy* is expressed in Starlark; the *mechanism* lives in Go. This allows users to define rate limiting that meets the needs of their use case without ToolHive needing to explicitly support every rate limiting pattern. + +### Use Case 6: Composing multiple concerns + +A single script handles optimizer + elicitation gate + PII scrubbing + rate limiting — behaviors that today require four different config surfaces: + +```python +def build_summary(tool_list): + cats = {} + for meta, fn in tool_list: + cat = meta.annotations.get("category", "general") + if cat not in cats: + cats[cat] = [] + cats[cat].append(meta.name) + return "Search for tools across: " + ", ".join( + "%s (%d tools)" % (c, len(ns)) for c, ns in cats.items() + ) + +all_tools = [] +tool_handlers = {} +tool_metadata = {} + +for name, backend in backends().items(): + for meta, fn in backend.tools(): + all_tools.append((meta, fn)) + tool_handlers[meta.name] = fn + tool_metadata[meta.name] = meta + +index = search_index(all_tools) +desc = build_summary(all_tools) + +def dispatch(args): + tool_name = args["tool_name"] + arguments = args["arguments"] + user = current_user() + + # Rate limit + allowed, retry_after = check_rate_limit( + key=user.sub + ":" + tool_name, limit=100, window=60, + ) + if not allowed: + return {"error": "Rate limited", "retry_after": retry_after} + + # Elicitation gate for non-readonly tools + meta = tool_metadata.get(tool_name) + if meta and not meta.annotations.get("readOnly", False): + decision = elicit("Approve call to '%s'?" % tool_name) + if decision.action != "accept": + return {"error": "Declined"} + + # Execute and scrub + result = tool_handlers[tool_name](arguments) + if "text" in result: + result["text"] = scrub_pii(result["text"]) + return result + +publish( + metadata(name="find_tool", description=desc, + parameters=FIND_TOOL_SCHEMA, annotations={}), + lambda args: {"results": index.search(args["query"])}, +) + +publish( + metadata(name="call_tool", description="Call a tool by name.", + parameters=CALL_TOOL_SCHEMA, annotations={}), + dispatch, +) +``` + +The ordering is explicit. The interactions are visible. There are no surprising feature interactions because the administrator wrote the interaction. + +--- + ## RFC Lifecycle ### Review History From 780b4b24f9abc35c10e9a93618ad25a999461ce9 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 19:10:50 -0700 Subject: [PATCH 15/19] Move image to repo-level images/ to fix naming validation The CI validator checks all new files under rfcs/ for the THV-#### naming convention. Move the image to images/ at the repo root. Co-Authored-By: Claude Opus 4.6 --- .../images => images}/vmcp-feature-dependencies.png | Bin rfcs/THV-0060-starlark-programmable-middleware.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {rfcs/images => images}/vmcp-feature-dependencies.png (100%) diff --git a/rfcs/images/vmcp-feature-dependencies.png b/images/vmcp-feature-dependencies.png similarity index 100% rename from rfcs/images/vmcp-feature-dependencies.png rename to images/vmcp-feature-dependencies.png diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index f958b0f..938abc5 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -28,7 +28,7 @@ Users want to combine features in ways that make sense for their deployment. But Every new feature enters a web of dependencies with existing features. As we add to this web, we have to think carefully about how each addition interacts with everything else ([excalidraw source](https://excalidraw.com/#json=C3Co-yHQMwzjrJptY7Qmv,mCqdzvMmerb6yZ0_gmt24g)): -![vMCP feature dependency graph](./images/vmcp-feature-dependencies.png) +![vMCP feature dependency graph](../images/vmcp-feature-dependencies.png) The cost is concrete — implicit interactions produce bugs: From be01816bd29e229d4bc799c8a427b2e18074d462 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 19:12:12 -0700 Subject: [PATCH 16/19] Move image to assets/0060/ per repo conventions Co-Authored-By: Claude Opus 4.6 --- .../0060}/vmcp-feature-dependencies.png | Bin rfcs/THV-0060-starlark-programmable-middleware.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {images => assets/0060}/vmcp-feature-dependencies.png (100%) diff --git a/images/vmcp-feature-dependencies.png b/assets/0060/vmcp-feature-dependencies.png similarity index 100% rename from images/vmcp-feature-dependencies.png rename to assets/0060/vmcp-feature-dependencies.png diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index 938abc5..c1bb24f 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -28,7 +28,7 @@ Users want to combine features in ways that make sense for their deployment. But Every new feature enters a web of dependencies with existing features. As we add to this web, we have to think carefully about how each addition interacts with everything else ([excalidraw source](https://excalidraw.com/#json=C3Co-yHQMwzjrJptY7Qmv,mCqdzvMmerb6yZ0_gmt24g)): -![vMCP feature dependency graph](../images/vmcp-feature-dependencies.png) +![vMCP feature dependency graph](../assets/0060/vmcp-feature-dependencies.png) The cost is concrete — implicit interactions produce bugs: From d75b92b516b3a6eadd45750ae6dfc3a76b67709b Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Fri, 27 Mar 2026 19:25:02 -0700 Subject: [PATCH 17/19] Add refactoring alternative to Alternatives Considered Add Alternative 2 covering Go code refactoring as a competing approach. Acknowledge its value while noting it doesn't address configurability or cross-cutting concerns. Include AI-driven development observation. Co-Authored-By: Claude Opus 4.6 --- rfcs/THV-0060-starlark-programmable-middleware.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index c1bb24f..9b402e0 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -576,7 +576,15 @@ Scripts have no access to secrets. Backend authentication is handled below the s - **Cons**: Interaction matrix grows quadratically. Bugs like #4287 from non-obvious interactions. Testing becomes intractable. - **Why not chosen**: Already causing problems at current feature count. -#### Alternative 2: Declarative pipeline (ordered stages) +#### Alternative 2: Refactor the existing Go code + +Invest in better code organization — clearer interfaces between features, a well-defined internal pipeline, better test coverage for combinations. + +- **Pros**: No new language or dependency. Directly improves code quality and maintainability. +- **Cons**: Addresses maintainability but not configurability — every new behavior still requires a code change and release. Code organization works best when concerns have clean boundaries, but vMCP's concerns are cross-cutting: the optimizer rewrites tool names that authorization policies reference, rate limiting must track names after conflict resolution, and an elicitation gate needs annotations that aggregation doesn't surface. These concerns cut across module boundaries rather than fitting neatly within them. Additionally, with AI-driven development, code quality in a complex, cross-cutting codebase is harder to police. A model where new capabilities ship as isolated built-in functions is more resistant to quality erosion — each built-in has a single, self-contained implementation. +- **Why not chosen**: Refactoring is valuable and should continue regardless, but it doesn't address the configurability gap. Administrators who need deployment-specific policies are still waiting for ToolHive to build them. + +#### Alternative 3: Declarative pipeline (ordered stages) Instead of a scripting language, make the config ordering explicit — a pipeline of named stages (like Envoy filter chains or Traefik middleware stacks). @@ -584,13 +592,13 @@ Instead of a scripting language, make the config ordering explicit — a pipelin - **Cons**: A declarative pipeline can express ordering and filtering, but cannot express computed values (dynamic descriptions based on available tools), conditional logic (different behavior based on annotations), or new synthetic tools (a `find_tool` with a generated description). Every new behavior still requires a new stage type implemented in Go. - **Why not chosen**: The problem isn't just ordering — it's that administrators need to express logic that varies per deployment. A pipeline makes ordering explicit but keeps the "new knob per behavior" problem. -#### Alternative 3: Starlark for composite tools only (THV-0051 as-is) +#### Alternative 4: Starlark for composite tools only (THV-0051 as-is) - **Pros**: Smaller scope - **Cons**: Misses the opportunity to unify. Interaction problem remains for optimizer + filter + rate limiting. Expanding scope later means a second migration. - **Why not chosen**: Design for the broader use case from day one. -#### Alternative 4: Use webhooks for everything +#### Alternative 5: Use webhooks for everything - **Pros**: Maximum flexibility, language-agnostic - **Cons**: External services for simple policies. Network latency on every call. Overkill for "hide these tools." From 97f78a76c3b2430a836e076b62be5b7545f9bd8a Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Mon, 30 Mar 2026 18:52:47 -0700 Subject: [PATCH 18/19] Address review feedback from yrobla - Fix priority_order.index() crash for unranked backends in default preset - Inline FIND_TOOL_SCHEMA and CALL_TOOL_SCHEMA in default preset sketch - Add when_unavailable parameter to elicit() for non-elicitation clients - Make preset/script/scriptFile mutual exclusion a hard validation error - Resolve error handling open question: MCP-standard isError response dicts - Keep existing decorators in Phase 1 POC, delete in later phases - Clarify session scope: runs once per session creation or Redis restore - Restructure rollout: safe capabilities first, optimizer + authz together - Add warning about handler dispatch bypassing Cedar authz (#4374) - Reference PR #4385 as interim fix for optimizer + authz bypass - Resolve authz open question: ship with optimizer, defer exact design - Add thv vmcp list-presets command Co-Authored-By: Claude Opus 4.6 (1M context) --- ...V-0060-starlark-programmable-middleware.md | 111 ++++++++++++++---- 1 file changed, 88 insertions(+), 23 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index 9b402e0..eee5f46 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -183,6 +183,8 @@ for name, backend in backends().items(): publish(meta, fn) ``` +**Note on authorization**: Handlers returned from `backend.tools()` dispatch directly to the backend, bypassing Cedar authz middleware. This means a handler saved in a dict (as in the optimizer pattern) can invoke tools the current user isn't authorized to use — the same root cause as [#4374](https://github.com/stacklok/toolhive/issues/4374). See [Interaction with authorization](#interaction-with-authorization) for details. The implementation plan addresses this by shipping optimizer + authz integration together in Phase 3. + #### Handling name collisions across backends Because the script sees which backend each tool comes from, it can handle collisions explicitly: @@ -201,6 +203,22 @@ for name, backend in backends().items(): publish(meta, fn) ``` +#### Error handling + +When a handler invokes a backend tool and it fails, the Go engine catches the error and returns an MCP-standard `isError` response dict: `{"isError": true, "content": [{"type": "text", "text": "..."}]}`. The script is not halted — the error is a value the script can inspect and react to. This enables decorator-style error recovery as plain Starlark: + +```python +def with_fallback(fn, fallback_fn): + def wrapper(args): + result = fn(args) + if result.get("isError"): + return fallback_fn(args) + return result + return wrapper +``` + +Retry wrappers, default responses, and other error policies are all expressible as userland decorators using the same mechanism. + #### Decorating handlers Since handlers are just functions, decoration is plain function wrapping: @@ -256,7 +274,7 @@ These are Go-implemented functions exposed to Starlark scripts. | Built-in | Signature | Description | |----------|-----------|-------------| | `search_index(tools)` | `search_index(list[(metadata, handler)]) → SearchIndex` | Builds a semantic search index over the tool list. Returns an object with `.search(query) → list[dict]`. | -| `elicit(message, schema)` | `elicit(message, schema={}) → struct(action, content)` | Prompts the user for a decision via MCP elicitation. | +| `elicit(message, schema, when_unavailable)` | `elicit(message, schema={}, when_unavailable) → struct(action, content)` | Prompts the user for a decision via MCP elicitation. `when_unavailable` is required and controls behavior when the client doesn't support elicitation: `"accept"` (permit the operation), `"reject"` (deny it), or `"error"` (halt the script). | | `config()` | `config() → dict` | Returns the vMCP config fields (`aggregation`, `optimizer`, etc.) as a read-only dict. Used by the `default` preset. | | `log(message)` | `log(message) → None` | Emits a structured audit log entry. | @@ -291,10 +309,11 @@ An important question is: how do people who don't want to write Starlark still u **Answer: presets.** A preset is a named, built-in Starlark script that replicates the behavior of today's config knobs. Presets are transparent — users can inspect the underlying Starlark source and fork it when they need customization: ```bash +thv vmcp list-presets thv vmcp show-preset default ``` -This prints the Starlark source. A user who needs 90% of a preset's behavior can copy it, modify the 10% they need, and use `sessionInit.script` or `sessionInit.scriptFile` instead. +`list-presets` shows available presets. `show-preset` prints the Starlark source for a given preset. A user who needs 90% of a preset's behavior can copy it, modify the 10% they need, and use `sessionInit.script` or `sessionInit.scriptFile` instead. #### Built-in presets @@ -306,9 +325,41 @@ A single `default` preset handles all existing config knobs. When no `sessionIni #### Sketch of the `default` preset -The `default` preset is the most complex part of this RFC — it must faithfully replicate the behavior of the existing config-driven system. Below is a sketch of what this script looks like. The exact implementation will be validated during the POC phase. +The `default` preset is the most complex part of this RFC — it must faithfully replicate the behavior of the existing config-driven system. The sketch below shows what we're aiming for. The exact nature of the built-ins — particularly for more distant phases like authz integration — is open to discussion and doesn't need to be resolved in this RFC. ```python +# --- Optimizer tool schemas (used when optimizer mode is enabled) --- +FIND_TOOL_SCHEMA = { + "type": "object", + "properties": { + "tool_description": { + "type": "string", + "description": "Description of the task or capability needed (e.g. 'web search', 'analyze CSV file'). Used for semantic similarity matching.", + }, + "tool_keywords": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional keywords for BM25 text search to narrow results. Combined with tool_description for hybrid search.", + }, + }, + "required": ["tool_description"], +} + +CALL_TOOL_SCHEMA = { + "type": "object", + "properties": { + "tool_name": { + "type": "string", + "description": "The name of the tool to execute (obtain from find_tool results).", + }, + "parameters": { + "type": "object", + "description": "Arguments required by the tool. Must match the tool's input schema from find_tool.", + }, + }, + "required": ["tool_name", "parameters"], +} + cfg = config() agg = cfg.get("aggregation", {}) opt = cfg.get("optimizer", None) @@ -367,6 +418,8 @@ for backend_name, backend in backends().items(): ) elif strategy == "priority": existing_backend = seen_names[meta.name] + if backend_name not in priority_order or existing_backend not in priority_order: + continue # unranked backends don't win collisions if priority_order.index(backend_name) > priority_order.index(existing_backend): continue # lower priority, skip # else: higher priority, will overwrite @@ -374,6 +427,11 @@ for backend_name, backend in backends().items(): seen_names[meta.name] = backend_name all_published.append((meta, fn)) +# --- Enforce Cedar authorization policies (Phase 3) --- +# Filters the tool set so handlers can only dispatch to authorized tools. +# This ensures the optimizer pattern doesn't bypass authz (#4374). +all_published = enforce_cedar_policies(all_published) + # --- Optimizer mode: publish find_tool/call_tool instead of raw tools --- if opt: index = search_index(all_published) @@ -442,7 +500,7 @@ sequenceDiagram Authz-->>Agent: CallToolResult ``` -The script runs **once** per session, not per request. `publish()` calls build up the tool set. The resulting `(metadata, handler)` pairs are used to construct the `MultiSession`, which handles all subsequent `tools/list` and `tools/call` requests. The authz middleware sits between the agent and the session, filtering and gating requests at runtime. +The script runs **once** per session — either when a session is created or when it's restored from Redis. The Starlark state is in-memory and not serialized, but can be recreated by re-running the script. Even though the script output could technically be shared across sessions today, running it once per session is a deliberate choice — it enables safe adoption of user-centric built-ins like `current_user()` in the future without requiring an architectural change. `publish()` calls build up the tool set. The resulting `(metadata, handler)` pairs are used to construct the `MultiSession`, which handles all subsequent `tools/list` and `tools/call` requests. The authz middleware sits between the agent and the session, filtering and gating requests at runtime. #### Where this fits in the architecture @@ -509,7 +567,7 @@ type SessionInitConfig struct { #### Existing config fields -`aggregation`, `compositeToolRefs`, and `optimizer` remain on `Config`. The `default` preset reads these fields and produces identical behavior. When a custom `sessionInit.script` or `sessionInit.scriptFile` is set, these fields are ignored (if both are set, vMCP logs a warning). +`aggregation`, `compositeToolRefs`, and `optimizer` remain on `Config`. The `default` preset reads these fields and produces identical behavior. When a custom `sessionInit.script` or `sessionInit.scriptFile` is set, these fields are ignored. Setting more than one of `preset`, `script`, or `scriptFile` is a validation error — vMCP rejects the configuration at load time. ## Security Considerations @@ -663,31 +721,40 @@ A fast, rough POC to validate the high-level design. The goal is to prove the pr - Implement `backends()`, `publish()`, `metadata()` built-ins in the Starlark engine - Session factory runs the script and constructs `MultiSession` from `publish()` results - Implement the `default` preset that reads existing config knobs (`aggregation`, `optimizer`, etc.) -- **Delete** the existing decorator-based code that supports these config knobs today (optimizer, filter, composite tools decorators) +- Run the Starlark engine alongside existing decorators for comparison testing - All existing tests must pass **except** those that test legacy composite tools (`compositeTools`, `compositeToolRefs`) - Update this RFC with any findings — design changes, missing built-ins, edge cases discovered -### Phase 2: Production implementation +### Phase 2: Safe capabilities -Take the learnings from the POC and implement for real. This is the production-quality version with proper error handling, tests, and documentation. +Ship the capabilities that don't interact with the authz boundary. These are the "safe" features that can be validated independently. -- Extend the Starlark engine from THV-0051 with production-quality `backends()`, `publish()`, `metadata()` built-ins -- Port `search_index()` from current optimizer implementation +- Production-quality `backends()`, `publish()`, `metadata()` built-ins +- Name resolution, filtering, and overrides via the `default` preset +- Rate limiting integration - `thv vmcp show-preset` command to inspect built-in presets - Config model: `sessionInit.preset`, `sessionInit.script`, `sessionInit.scriptFile` -- Preset equivalence tests: verify the `default` preset produces identical behavior to the old config-driven system -- Remove optimizer, filter, and composite tools decorators — the session init script is the single implementation +- Preset equivalence tests for the capabilities in scope +- Remove the decorator code for features replaced in this phase + +### Phase 3: Optimizer + authz integration + +Ship the optimizer and authz capabilities together so the relationship between them is explicit. Today, the optimizer bypasses Cedar because handlers dispatch directly to backends ([#4374](https://github.com/stacklok/toolhive/issues/4374), interim fix in [PR #4385](https://github.com/stacklok/toolhive/pull/4385)). By shipping them together, the script can enforce Cedar policies on the tool set before the optimizer builds its dispatch table (e.g. `enforce_cedar_policies(all_published)`). -### Phase 3: Deprecate composite tools +- `search_index()` built-in ported from current optimizer implementation +- Authz built-in (e.g. `enforce_cedar_policies()`) that filters the `(metadata, handler)` list +- Updated `default` preset with optimizer + authz integration +- Preset equivalence tests for optimizer behavior +- Remove remaining decorator code +- The exact design of the authz built-ins will be detailed in a follow-up RFC + +### Phase 4: Deprecate composite tools, ship and document - Mark `compositeTools` and `compositeToolRefs` as deprecated - Log deprecation warnings when these fields are used - Document migration path from declarative composite tools to Starlark scripts - -### Phase 4: Ship and document - - E2E tests for custom scripts in K8s via ConfigMap -- Documentation: user guide, built-in reference, composite tools migration guide, advanced use cases +- Documentation: user guide, built-in reference, migration guide, advanced use cases New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of scope for this RFC. The programming model makes them straightforward to add as follow-up work. @@ -709,11 +776,7 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc ## Open Questions -1. **Error handling in handler functions**: When a handler function invokes a backend tool and it fails, what should happen? Options include: (a) the handler returns an error dict to the agent, (b) a `with_fallback(fn, fallback_fn)` pattern for decorator-style error recovery, (c) preset parameters for common error policies (retry N times, fall back to a default response). - -2. **Should authz decisions move into Starlark?** Authorization (Cedar) and session initialization (Starlark) remain entirely separate systems. This RFC reduces config knob interactions significantly and makes most of them explicit, but the "who sees what?" question still requires reasoning across both systems. The interaction between the optimizer and Cedar illustrates the problem: enabling the optimizer replaces real tool names with `find_tool` / `call_tool`, which silently breaks Cedar policies that reference the original names ([stacklok/toolhive#4373](https://github.com/stacklok/toolhive/issues/4373))); and `find_tool` returns tools the caller isn't authorized to use, because Cedar gates `tools/call` but doesn't filter search results inside a handler ([stacklok/toolhive#4374](https://github.com/stacklok/toolhive/issues/4374)). Neither system is aware of the other. Pulling authz decisions into the script (e.g., a `current_user()` built-in combined with policy logic) would unify the model but raises questions about Cedar's role and the trust boundary. Worth exploring once the base programming model is proven. - -3. **Sessionless MCP requests**: What happens when MCP supports requests without sessions? Do we have to run this heavy script on every request? We could actually run the script once at startup, since it does not depend on request-time information. However, if we fold in authz concerns from above, then `current_user()` will be request-time information. We could cheat around this by recommending all logic which depends on `current_user()` be placed at the end of the script. When that's encountered during startup, we block and restore the state on each request. Alternatively, we could support two different scripts. One for initialization and one per-request. +1. **Sessionless MCP requests**: What happens when MCP supports requests without sessions? Do we have to run this heavy script on every request? We could actually run the script once at startup, since it does not depend on request-time information. However, if we fold in authz concerns from above, then `current_user()` will be request-time information. We could cheat around this by recommending all logic which depends on `current_user()` be placed at the end of the script. When that's encountered during startup, we block and restore the state on each request. Alternatively, we could support two different scripts. One for initialization and one per-request. ## References @@ -721,6 +784,7 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc - [THV-0057: Rate Limiting](./THV-0057-rate-limiting.md) — rate limiting mechanism - [THV-0017: Dynamic Webhook Middleware](./THV-0017-dynamic-webhook-middleware.md) — external webhook integration - [stacklok-epics#213](https://github.com/stacklok/stacklok-epics/issues/213) — Dynamic Webhook Middleware epic +- [stacklok/toolhive#4385](https://github.com/stacklok/toolhive/pull/4385) — Interim fix for optimizer + authz bypass (#4374) - [Optimizer discoverability discussion](https://stacklok.slack.com/archives/C09L9QF47EU/p1774392171855569) — Slack thread - [Starlark Language Specification](https://github.com/bazelbuild/starlark/blob/master/spec.md) - [starlark-go Implementation](https://github.com/google/starlark-go) @@ -789,6 +853,7 @@ def with_approval_gate(fn, tool_name): decision = elicit( "Tool '%s' may modify data. Approve?" % tool_name, schema={"type": "object", "properties": {"reason": {"type": "string"}}}, + when_unavailable="reject", ) if decision.action != "accept": return {"error": "Declined by user"} @@ -953,7 +1018,7 @@ def dispatch(args): # Elicitation gate for non-readonly tools meta = tool_metadata.get(tool_name) if meta and not meta.annotations.get("readOnly", False): - decision = elicit("Approve call to '%s'?" % tool_name) + decision = elicit("Approve call to '%s'?" % tool_name, when_unavailable="reject") if decision.action != "accept": return {"error": "Declined"} From a4dcadaa4176b611f107b7d5a4f6ebc856e3226e Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Tue, 7 Apr 2026 15:02:03 -0700 Subject: [PATCH 19/19] Address second round of review feedback on RFC-0060 - Restructure implementation plan into 3 phases: POC (with rate limiting benchmark), backwards compatibility, new functionality - Update rate limiting interaction section: THV-0057 ships as standalone middleware first, Starlark built-in benchmarked later - Clarify current_user() returns same value for session lifetime - Add security section note on identity-based filtering being intentional and complementary to Cedar - Rewrite Open Question 1: note two-hook model as future direction for sessionless MCP, remove ordering heuristic Co-Authored-By: Claude Opus 4.6 (1M context) --- ...V-0060-starlark-programmable-middleware.md | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/rfcs/THV-0060-starlark-programmable-middleware.md b/rfcs/THV-0060-starlark-programmable-middleware.md index eee5f46..42e6805 100644 --- a/rfcs/THV-0060-starlark-programmable-middleware.md +++ b/rfcs/THV-0060-starlark-programmable-middleware.md @@ -3,7 +3,7 @@ - **Status**: Draft - **Author(s)**: Jeremy Drouillard (@jerm-dro) - **Created**: 2026-03-24 -- **Last Updated**: 2026-03-27 +- **Last Updated**: 2026-04-07 - **Target Repository**: toolhive - **Related Issues**: [stacklok-epics#213](https://github.com/stacklok/stacklok-epics/issues/213) - **Related**: [THV-0051 (Starlark Scripted Tools)](./THV-0051-starlark-scripted-tools.md) — this RFC broadens the scope of Starlark in vMCP from composite tool workflows to a unified session initialization model @@ -298,9 +298,14 @@ The programming model makes it straightforward to add new capabilities as simple | Built-in | Signature | Description | |----------|-----------|-------------| -| `current_user()` | `current_user() → struct(sub, email, groups)` | Returns the authenticated user's identity. The user is known at init time, but this built-in is deferred to a future version. | | `scrub_pii(text)` | `scrub_pii(text) → string` | Redacts PII patterns (emails, phones, SSNs, credit cards) from text. | -| `check_rate_limit(key, limit, window)` | `check_rate_limit(key, limit, window) → (bool, int)` | Checks a token bucket counter in Redis. Returns `(allowed, retry_after_seconds)`. | + +The following built-ins are planned for Phase 2 (backwards compatibility) and Phase 1 (proof of concept) respectively: + +| Built-in | Signature | Phase | Description | +|----------|-----------|-------|-------------| +| `current_user()` | `current_user() → struct(sub, email, groups)` | Phase 2 | Returns the authenticated user's identity. Returns the same value for the lifetime of the session — the user who created it. Needed for feature parity with existing identity-aware behavior. | +| `check_rate_limit(key, limit, window)` | `check_rate_limit(key, limit, window) → (bool, int)` | Phase 1 (POC) | Checks a token bucket counter in Redis. Returns `(allowed, retry_after_seconds)`. Used to benchmark Starlark vs. native middleware performance. | ### Presets: Making it Easy for Non-Power-Users @@ -541,10 +546,14 @@ Both coexist. A request passes through webhooks first (external policy), then re #### Interaction with rate limiting -THV-0057's Redis-backed token bucket is the *mechanism*. A future `check_rate_limit()` built-in could expose it to scripts. Once available, the *policy* could be: +THV-0057's Redis-backed token bucket ships first as standalone middleware, covering both `MCPServers` and `MCPRemoteProxy` endpoints. This gives us production data on usage patterns and performance before committing to a Starlark-based approach. + +Once the middleware is stable, a `check_rate_limit()` built-in can expose the same Redis token bucket to Starlark scripts. The plan is to benchmark both implementations — middleware vs. Starlark built-in with the same backing mechanism — to compare implementation ease and performance. The results will inform how heavily we lean on Starlark for future capabilities. -1. **Config-driven**: The `default` preset reads `rateLimiting` from config and applies limits internally -2. **Script-driven**: Custom scripts implement context-aware rate limiting using the built-in +Once the built-in is available, the *policy* could be: + +1. **Config-driven**: The `default` preset reads `rateLimiting` from config and applies limits internally (same behavior as the standalone middleware) +2. **Script-driven**: Custom scripts implement context-aware rate limiting using the built-in (e.g., role-based limits, per-backend policies) ### API Changes @@ -592,6 +601,8 @@ type SessionInitConfig struct { **Trust model**: Session initialization scripts are written by administrators, not end users. An administrator who can write a Starlark script already has the authority to configure vMCP. +**Identity-based filtering**: With `current_user()`, scripts can filter or shape tools based on user identity (e.g., `if "admin" in current_user().groups`). This is intentional and complementary to Cedar — there is no one-size-fits-all approach. Administrators who prefer declarative access control can continue using Cedar policies. Those who need something simpler or prefer an imperative approach can express it in the script. Both are valid and coexist. + ### Data Security - Scripts cannot access filesystem, network, or environment variables (Starlark sandbox) @@ -716,47 +727,39 @@ New built-in functions can be added without breaking existing scripts. New prese ### Phase 1: Proof of concept -A fast, rough POC to validate the high-level design. The goal is to prove the programming model works end-to-end and surface any surprises before committing to a production implementation. +A fast, rough POC to answer two questions: *does the programming model work end-to-end?* and *is Starlark execution fast enough?* - Implement `backends()`, `publish()`, `metadata()` built-ins in the Starlark engine - Session factory runs the script and constructs `MultiSession` from `publish()` results -- Implement the `default` preset that reads existing config knobs (`aggregation`, `optimizer`, etc.) -- Run the Starlark engine alongside existing decorators for comparison testing -- All existing tests must pass **except** those that test legacy composite tools (`compositeTools`, `compositeToolRefs`) -- Update this RFC with any findings — design changes, missing built-ins, edge cases discovered +- Implement `check_rate_limit()` built-in backed by the same Redis token bucket as THV-0057 +- Benchmark native Go middleware vs. Starlark built-in for rate limiting — same mechanism, same config surface, comparing implementation ease and performance overhead +- Update this RFC with findings — design changes, missing built-ins, performance data -### Phase 2: Safe capabilities +### Phase 2: Backwards compatibility -Ship the capabilities that don't interact with the authz boundary. These are the "safe" features that can be validated independently. +Full feature parity with the existing config-driven system. This phase is substantial and will ship incrementally. - Production-quality `backends()`, `publish()`, `metadata()` built-ins -- Name resolution, filtering, and overrides via the `default` preset -- Rate limiting integration -- `thv vmcp show-preset` command to inspect built-in presets -- Config model: `sessionInit.preset`, `sessionInit.script`, `sessionInit.scriptFile` -- Preset equivalence tests for the capabilities in scope -- Remove the decorator code for features replaced in this phase - -### Phase 3: Optimizer + authz integration - -Ship the optimizer and authz capabilities together so the relationship between them is explicit. Today, the optimizer bypasses Cedar because handlers dispatch directly to backends ([#4374](https://github.com/stacklok/toolhive/issues/4374), interim fix in [PR #4385](https://github.com/stacklok/toolhive/pull/4385)). By shipping them together, the script can enforce Cedar policies on the tool set before the optimizer builds its dispatch table (e.g. `enforce_cedar_policies(all_published)`). - +- `current_user()` built-in for identity-aware policies +- Name resolution, filtering, overrides, conflict resolution via the `default` preset - `search_index()` built-in ported from current optimizer implementation -- Authz built-in (e.g. `enforce_cedar_policies()`) that filters the `(metadata, handler)` list +- Authz built-in (e.g. `enforce_cedar_policies()`) shipped together with optimizer to avoid the [#4374](https://github.com/stacklok/toolhive/issues/4374) bypass (interim fix in [PR #4385](https://github.com/stacklok/toolhive/pull/4385)) - Updated `default` preset with optimizer + authz integration -- Preset equivalence tests for optimizer behavior -- Remove remaining decorator code -- The exact design of the authz built-ins will be detailed in a follow-up RFC +- `thv vmcp list-presets` / `show-preset` commands +- Config model: `sessionInit.preset`, `sessionInit.script`, `sessionInit.scriptFile` +- Preset equivalence tests — all existing behavior reproduced identically +- Mark `compositeTools` and `compositeToolRefs` as deprecated; log warnings; document migration path +- Remove decorator code for replaced features +- Documentation: user guide, built-in reference, migration guide +- E2E tests for custom scripts in K8s via ConfigMap -### Phase 4: Deprecate composite tools, ship and document +### Phase 3: New functionality -- Mark `compositeTools` and `compositeToolRefs` as deprecated -- Log deprecation warnings when these fields are used -- Document migration path from declarative composite tools to Starlark scripts -- E2E tests for custom scripts in K8s via ConfigMap -- Documentation: user guide, built-in reference, migration guide, advanced use cases +Capabilities enabled by the programming model that don't exist today. -New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of scope for this RFC. The programming model makes them straightforward to add as follow-up work. +- `scrub_pii()` built-in for response redaction +- Code mode: tools and skills enabling LLMs to build Starlark workflows predictably within the sandbox +- Additional built-ins as use cases emerge ## Testing Strategy @@ -776,7 +779,7 @@ New built-in functions like `scrub_pii()` and `check_rate_limit()` are out of sc ## Open Questions -1. **Sessionless MCP requests**: What happens when MCP supports requests without sessions? Do we have to run this heavy script on every request? We could actually run the script once at startup, since it does not depend on request-time information. However, if we fold in authz concerns from above, then `current_user()` will be request-time information. We could cheat around this by recommending all logic which depends on `current_user()` be placed at the end of the script. When that's encountered during startup, we block and restore the state on each request. Alternatively, we could support two different scripts. One for initialization and one per-request. +1. **Sessionless MCP requests**: What happens when MCP supports requests without sessions? The current single-script model works well for session-scoped execution, but sessionless requests would require running the script per-request or once at startup. A promising direction is a two-hook model: `on_session_init()` for tool shape and presentation (runs once), `on_request()` for per-request concerns like rate limiting and user-specific filtering. This cleanly maps to the distinction the RFC already makes and would survive the sessionless transition without script-ordering footguns. We'll design the right model when concrete requirements emerge. ## References