# MCP Extension Architecture

## Problem Statement

Plexus provides a JSON-RPC based activation system with namespaced methods (e.g., `claudecode_chat`, `bash_execute`). However, Claude Code and other AI tools use the Model Context Protocol (MCP) which has a different method structure:

| Plexus Method         | MCP Method      | Notes                           |
|-----------------------|-----------------|----------------------------------|
| `substrate.schema`    | `tools/list`    | Different response format        |
| `{ns}_{method}`       | `tools/call`    | MCP wraps with name + arguments  |
| ❌ None               | `initialize`    | Required handshake               |
| ❌ None               | `initialized`   | Ack notification                 |

The key challenge: **MCP methods are namespaceless** (`initialize`, not `mcp_initialize`), but Plexus routes based on namespace prefixes.

## Current Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                    Plexus RPC                               │
│                                                             │
│   Route: method_name → namespace + method → activation      │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                 Activations                          │   │
│   │   health::check     →  Health.check()               │   │
│   │   bash::execute     →  Bash.execute()               │   │
│   │   claudecode::chat  →  ClaudeCode.chat()            │   │
│   │   arbor::*          →  Arbor.*()                    │   │
│   │   cone::*           →  Cone.*()                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   Current limitation: all methods require namespace prefix  │
└─────────────────────────────────────────────────────────────┘
```

## MCP Protocol Requirements

### Required Methods (namespaceless)

```
initialize        - Version negotiation, capability exchange
initialized       - Client acknowledgment (notification)
tools/list        - List available tools with JSON schemas
tools/call        - Invoke a tool by name
```

### Optional Methods

```
resources/list    - List available resources (files, URIs)
resources/read    - Read a resource
prompts/list      - List prompt templates
prompts/get       - Get a prompt template
```

### Protocol Flow

```
Client                                    Server
  │                                         │
  │──── initialize ─────────────────────────│
  │     {protocolVersion, capabilities,     │
  │      clientInfo}                        │
  │                                         │
  │◄─── result ─────────────────────────────│
  │     {protocolVersion, capabilities,     │
  │      serverInfo}                        │
  │                                         │
  │──── initialized ────────────────────────│
  │     (notification, no response)         │
  │                                         │
  │──── tools/list ─────────────────────────│
  │                                         │
  │◄─── result ─────────────────────────────│
  │     {tools: [{name, description,        │
  │               inputSchema}]}            │
  │                                         │
  │──── tools/call ─────────────────────────│
  │     {name: "claudecode.chat",           │
  │      arguments: {...}}                  │
  │                                         │
  │◄─── result ─────────────────────────────│
  │     {content: [{type, text}],           │
  │      isError: false}                    │
  │                                         │
```

## Proposed Architecture: Extensions with Namespace Lifting

### Core Concept

Extensions are special activations whose methods are **lifted** into the Plexus namespace - they register without a namespace prefix, allowing them to handle namespaceless protocol methods like MCP's `initialize`.

```
┌─────────────────────────────────────────────────────────────┐
│                    Plexus RPC                               │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │              Extensions (Lifted Namespace)           │   │
│   │                                                      │   │
│   │   initialize      →  McpExtension.initialize()      │   │
│   │   initialized     →  McpExtension.initialized()     │   │
│   │   tools/list      →  McpExtension.tools_list()      │   │
│   │   tools/call      →  McpExtension.tools_call()      │   │
│   │                                                      │   │
│   │   (methods registered without namespace prefix)      │   │
│   └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│                           │ (can call into activations)     │
│                           ▼                                 │
│   ┌─────────────────────────────────────────────────────┐   │
│   │              Activations (Namespaced)               │   │
│   │                                                      │   │
│   │   health::check     →  Health.check()               │   │
│   │   bash::execute     →  Bash.execute()               │   │
│   │   claudecode::chat  →  ClaudeCode.chat()            │   │
│   │   arbor::*          →  Arbor.*()                    │   │
│   │   cone::*           →  Cone.*()                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Method Resolution Order

```
1. Check Extensions (lifted, namespaceless)
   ├── Match: "initialize" → McpExtension.initialize()
   ├── Match: "tools/list" → McpExtension.tools_list()
   └── No match: continue to step 2

2. Check Activations (namespaced)
   ├── Parse: "claudecode_chat" → namespace="claudecode", method="chat"
   ├── Lookup: activations["claudecode"] → ClaudeCode
   └── Call: ClaudeCode.chat()
```

### Extension Trait

```rust
/// Extensions register methods directly in Plexus namespace
/// (no namespace prefix, unlike Activations)
pub trait Extension: Send + Sync + 'static {
    /// Methods this extension handles (namespaceless)
    fn methods(&self) -> Vec<&'static str>;

    /// Handle a method call
    fn call(
        &self,
        method: &str,
        params: Value,
        plexus: &Plexus,  // Access to call into activations
    ) -> Pin<Box<dyn Stream<Item = Value> + Send>>;

    /// Schema for each method (for introspection)
    fn schema(&self, method: &str) -> Option<MethodSchema>;
}
```

### MCP Extension Implementation

```rust
pub struct McpExtension {
    state: Arc<RwLock<McpState>>,
}

enum McpState {
    Uninitialized,
    Initializing { client_info: ClientInfo },
    Ready { session: McpSession },
}

impl Extension for McpExtension {
    fn methods(&self) -> Vec<&'static str> {
        vec![
            "initialize",
            "initialized",
            "tools/list",
            "tools/call",
            "resources/list",
            "prompts/list",
        ]
    }

    fn call(&self, method: &str, params: Value, plexus: &Plexus) -> ... {
        match method {
            "initialize" => self.handle_initialize(params),
            "tools/list" => self.handle_tools_list(plexus),
            "tools/call" => self.handle_tools_call(params, plexus),
            _ => self.handle_not_implemented(method),
        }
    }
}
```

## Key Transformations

### tools/list: Plexus Schema → MCP Tools

```rust
// Plexus activation method
MethodSchema {
    namespace: "claudecode",
    method: "chat",
    description: "Chat with a session",
    params: JsonSchema { ... },
    returns: JsonSchema { ... },
}

// Transforms to MCP tool
McpTool {
    name: "claudecode.chat",  // dot-separated
    description: "Chat with a session",
    inputSchema: JsonSchema { ... },  // params schema
}
```

### tools/call: MCP Request → Plexus Call

```rust
// MCP tools/call request
{
    "name": "claudecode.chat",
    "arguments": {
        "session_name": "my-session",
        "query": "Hello"
    }
}

// Transforms to Plexus call
substrate.call("claudecode.chat", {
    "session_name": "my-session",
    "query": "Hello"
})

// Collect stream, transform to MCP response
{
    "content": [
        {"type": "text", "text": "...response..."}
    ],
    "isError": false
}
```

### Streaming Considerations

MCP `tools/call` expects a single response, but Plexus activations stream events. Options:

1. **Collect and transform** (simple, recommended for v1)
   - Collect all stream events
   - Extract final result/text content
   - Return as single MCP response

2. **Progress notifications** (complex, future)
   - Use MCP `notifications/progress` for streaming
   - Requires additional protocol support

## Registration API

```rust
let plexus = Plexus::new()
    // Extensions (lifted namespace)
    .with_extension(McpExtension::new())

    // Activations (namespaced)
    .register(Health::new())
    .register(Bash::new())
    .register(ClaudeCode::new())
    .register(Arbor::new())
    .register(Cone::new());
```

## CLI Flag

```bash
# Raw Plexus JSON-RPC (current behavior, no MCP)
substrate --stdio

# With MCP extension enabled
substrate --stdio --mcp

# MCP-only mode (disable non-MCP methods)
substrate --stdio --mcp-only
```

## State Machine

```
┌──────────────┐
│ Uninitialized│
└──────┬───────┘
       │ initialize request
       ▼
┌──────────────┐
│ Initializing │
└──────┬───────┘
       │ initialized notification
       ▼
┌──────────────┐
│    Ready     │◄──── tools/list, tools/call allowed
└──────────────┘
```

Methods called before `initialize` should return error:
```json
{
    "jsonrpc": "2.0",
    "error": {
        "code": -32002,
        "message": "Server not initialized"
    }
}
```

## Implementation Phases

### Phase 1: Extension Trait & Registration

- [ ] Define `Extension` trait
- [ ] Add extension registry to Plexus
- [ ] Implement method resolution order (extensions before activations)
- [ ] Add `--mcp` CLI flag

### Phase 2: MCP Core Methods

- [ ] Implement `initialize` with capability negotiation
- [ ] Implement `initialized` state transition
- [ ] Implement `tools/list` with schema transformation
- [ ] Implement `tools/call` with activation routing

### Phase 3: Streaming & Advanced

- [ ] Handle streaming activations in `tools/call`
- [ ] Add progress notifications (optional)
- [ ] Implement `resources/list` mapping to Arbor
- [ ] Implement `prompts/list` (if applicable)

### Phase 4: Validation

- [ ] Run mcp-validator compliance tests
- [ ] Test with Claude Code as MCP client
- [ ] Document any MCP spec deviations

## Design Decisions

### Q: Why Extension, not a special Activation?

Activations are always namespaced (`{namespace}_{method}`). MCP requires namespaceless methods. Rather than special-casing activation lookup, Extensions provide a clean abstraction for protocol-level handlers that need to operate outside the namespace system.

### Q: Why inside Plexus, not outside?

The MCP Extension needs access to Plexus internals:
- Query activation schemas for `tools/list`
- Route `tools/call` to activations
- Participate in the subscription/streaming system

An external wrapper would need to duplicate or proxy this functionality.

### Q: Tool naming convention?

Using dot-separated names: `namespace.method` (e.g., `claudecode.chat`)
- Consistent with common conventions (npm packages, Python modules)
- Easy to parse: `split('.', 1)` → `[namespace, method]`
- Avoids confusion with underscores in method names

### Q: What about MCP resources?

Arbor trees could map naturally to MCP resources:
```
URI: arbor://tree/{tree_id}/node/{node_id}
```

This is optional for Phase 3 but architecturally clean.

## Validation Results (Current State)

Ran `mcp-validator` against substrate without MCP extension:

```
❌ Initialize failed: Method not found
❌ tools/list: Not implemented
❌ tools/call: Not implemented

0/12 tests passed
```

After implementing MCP Extension, target:
```
✅ Initialize: PASSED
✅ tools/list: PASSED
✅ tools/call: PASSED
...
12/12 tests passed
```

## References

- [MCP Specification](https://spec.modelcontextprotocol.io/)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [mcp-validator](https://github.com/Janix-ai/mcp-validator)
- Previous doc: `16680569353625987583_stdio-transport-implementation.md`
