75 lines
4.2 KiB
Markdown
75 lines
4.2 KiB
Markdown
|
|
# Task ID: 114
|
||
|
|
|
||
|
|
**Title:** ProxyModel v2: Code-based MCP middleware plugin system
|
||
|
|
|
||
|
|
**Status:** done
|
||
|
|
|
||
|
|
**Dependencies:** None
|
||
|
|
|
||
|
|
**Priority:** high
|
||
|
|
|
||
|
|
**Description:** Redesign the ProxyModel framework from a YAML-configured content transformation pipeline into a full code-based MCP middleware plugin system. Proxy models become TypeScript files that can intercept any MCP request/response, create synthetic tools, maintain per-session state, and compose via multiple inheritance with compile-time conflict detection. The existing gate functionality (begin_session, tools/list filtering, prompt scoring, ungating) becomes the first proxy model implementation, proving the framework works by implementing gate entirely as a plugin with zero gate-specific code in router.ts.
|
||
|
|
|
||
|
|
**Details:**
|
||
|
|
|
||
|
|
## Vision
|
||
|
|
|
||
|
|
A proxy model is a TypeScript code file (not YAML) that acts as full MCP middleware. It can:
|
||
|
|
- Intercept any MCP request (initialize, tools/list, tools/call, resources/*, prompts/*)
|
||
|
|
- Modify any response before it reaches the client
|
||
|
|
- Create synthetic tools (e.g. begin_session doesn't exist upstream)
|
||
|
|
- Maintain per-session state (gated/ungated, accumulated tags, etc.)
|
||
|
|
- Access project resources (prompts, servers, config)
|
||
|
|
- Transform content (what stages do today: paginate, section-split, etc.)
|
||
|
|
|
||
|
|
## Key design decisions
|
||
|
|
|
||
|
|
1. Code not YAML: Proxy models live as .ts files in a known directory (e.g. ~/.mcpctl/proxymodels/). File exists = model exists. No create/delete via CLI.
|
||
|
|
2. Stages deprecated: No separate stage resource. Content transformation is just code inside the proxy model.
|
||
|
|
3. Multiple inheritance: A model can extend [gate, subindex] to compose behaviors from multiple parents. Conflicts (two parents intercepting the same method incompatibly) detected at load/compile time, not runtime.
|
||
|
|
4. Gate is just a proxy model: The ~300 lines of gate logic in router.ts move into a gate.ts proxy model file. Router becomes thin plumbing (~100 lines).
|
||
|
|
5. gated:true replaced by proxyModel field: Projects get a proxyModel: gate field. If the assigned model implements gating, the project is gated. No separate boolean.
|
||
|
|
6. Discoverable as resources: mcpctl get proxymodels lists available models (discovered from files). mcpctl describe proxymodel gate shows details. But no create/delete commands.
|
||
|
|
7. Attached to projects: mcpctl edit project foo --proxyModel gate or via apply YAML.
|
||
|
|
|
||
|
|
## Framework interface (sketch)
|
||
|
|
|
||
|
|
export interface ProxyModelContext {
|
||
|
|
session: SessionState;
|
||
|
|
project: ProjectConfig;
|
||
|
|
upstream: UpstreamClient;
|
||
|
|
llm?: LLMProvider;
|
||
|
|
cache?: CacheProvider;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ProxyModel {
|
||
|
|
name: string;
|
||
|
|
extends?: string[];
|
||
|
|
onInitialize?(ctx, request): Promise<InitializeResult>;
|
||
|
|
onToolsList?(ctx): Promise<Tool[]>;
|
||
|
|
onToolCall?(ctx, name, args): Promise<ToolResult | null>;
|
||
|
|
onResourceRead?(ctx, uri): Promise<ResourceContent | null>;
|
||
|
|
transformContent?(ctx, content, contentType): Promise<string>;
|
||
|
|
createSessionState?(): Record<string, unknown>;
|
||
|
|
}
|
||
|
|
|
||
|
|
## Migration path
|
||
|
|
|
||
|
|
1. Define the ProxyModel TypeScript interface
|
||
|
|
2. Implement the plugin loader (discover .ts files, compile, validate inheritance, detect conflicts)
|
||
|
|
3. Implement the router integration (router delegates to loaded proxy model)
|
||
|
|
4. Extract gate logic from router.ts into gate.ts proxy model
|
||
|
|
5. Extract content pipeline (passthrough, paginate, section-split) into proxy model code
|
||
|
|
6. Add proxyModel field to Project schema (replaces gated boolean)
|
||
|
|
7. Add CLI: get proxymodels, describe proxymodel, edit project --proxyModel
|
||
|
|
8. Add smoke tests: gate proxy model produces identical behavior to current hardcoded gate
|
||
|
|
9. Deprecate gated field (backward compat: gated:true maps to proxyModel:gate)
|
||
|
|
|
||
|
|
## Supersedes
|
||
|
|
|
||
|
|
This task supersedes deferred tasks 83, 85-97, 98-99, 103, 104, 110, 111-112 which assumed the old YAML/stage architecture.
|
||
|
|
|
||
|
|
**Test Strategy:**
|
||
|
|
|
||
|
|
1. Gate proxy model smoke test: identical behavior to current hardcoded gate (begin_session, tools/list filtering, ungating). 2. Composition test: model extending [gate, paginate] inherits both behaviors. 3. Conflict detection test: two parents intercepting same hook differently = compile-time error. 4. Discovery test: drop a .ts file in proxymodels dir, mcpctl get proxymodels shows it. 5. Existing smoke tests (proxy-pipeline.test.ts) pass unchanged after migration.
|