---
phase: 03-handler-middleware-and-server-integration
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/types/protocol.rs
  - src/server/tasks.rs
  - src/server/mod.rs
  - src/server/builder.rs
  - src/server/core.rs
  - crates/pmcp-tasks/Cargo.toml
autonomous: true
requirements:
  - INTG-01
  - INTG-02
  - INTG-10

must_haves:
  truths:
    - "CallToolRequest has an optional task field that deserializes from JSON-RPC tools/call requests with task augmentation"
    - "ToolInfo has an optional execution field that serializes into tools/list responses"
    - "ClientRequest enum has four new variants (TasksGet, TasksResult, TasksList, TasksCancel) that parse from JSON-RPC methods"
    - "ServerCoreBuilder.with_task_store() stores the task store and auto-configures experimental.tasks capability"
    - "ServerCore::new() accepts an optional task store parameter"
  artifacts:
    - path: "src/types/protocol.rs"
      provides: "task field on CallToolRequest, execution field on ToolInfo, four task ClientRequest variants"
      contains: "TasksGet"
    - path: "src/server/builder.rs"
      provides: "with_task_store() builder method"
      contains: "with_task_store"
    - path: "src/server/core.rs"
      provides: "task_store field on ServerCore"
      contains: "task_store"
  key_links:
    - from: "src/server/builder.rs"
      to: "src/server/core.rs"
      via: "passes task_store through ServerCore::new()"
      pattern: "task_store"
    - from: "src/types/protocol.rs"
      to: "crates/pmcp-tasks/src/types/params.rs"
      via: "ClientRequest task variants use serde_json::Value for params (avoid circular dep)"
      pattern: "TasksGet"
---

<objective>
Add protocol-level task support types to pmcp: task field on CallToolRequest, execution field on ToolInfo, four task routing variants on ClientRequest, and with_task_store() on the builder.

Purpose: Establish the protocol and builder foundation so Plan 02 can wire task routing and interception logic.
Output: Modified protocol types, builder with task store support, ServerCore with task_store field.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-plan.md
@./.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-handler-middleware-and-server-integration/03-RESEARCH.md
@.planning/phases/03-handler-middleware-and-server-integration/03-CONTEXT.md
@src/types/protocol.rs
@src/server/builder.rs
@src/server/core.rs
@crates/pmcp-tasks/Cargo.toml
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add task and execution fields to protocol types and ClientRequest variants</name>
  <files>src/types/protocol.rs</files>
  <action>
Modify `src/types/protocol.rs` to add:

1. **Add `task` field to `CallToolRequest`:**
   ```rust
   /// Task augmentation parameters (experimental MCP Tasks).
   /// When present, the server creates a task and returns CreateTaskResult
   /// instead of CallToolResult. Uses serde_json::Value to avoid circular
   /// crate dependency (pmcp-tasks depends on pmcp).
   #[serde(skip_serializing_if = "Option::is_none")]
   pub task: Option<Value>,
   ```
   This MUST be `Option<Value>` (not a pmcp-tasks type) because pmcp-tasks depends on pmcp -- using a pmcp-tasks type here would create a circular dependency. The Value is parsed as `TaskParams` in core.rs where pmcp-tasks is available.

2. **Add `execution` field to `ToolInfo`:**
   ```rust
   /// Execution metadata declaring task support level (experimental MCP Tasks).
   /// Uses serde_json::Value to avoid circular crate dependency.
   #[serde(skip_serializing_if = "Option::is_none")]
   pub execution: Option<Value>,
   ```
   Same rationale as above -- Value type avoids circular dep. Tools set this via `serde_json::to_value(ToolExecution { task_support: TaskSupport::Required })`.

3. **Add four task endpoint variants to `ClientRequest` enum:**
   ```rust
   /// Get task status (tasks/get)
   #[serde(rename = "tasks/get")]
   TasksGet(Value),
   /// Get task result (tasks/result)
   #[serde(rename = "tasks/result")]
   TasksResult(Value),
   /// List tasks (tasks/list)
   #[serde(rename = "tasks/list")]
   TasksList(Value),
   /// Cancel a task (tasks/cancel)
   #[serde(rename = "tasks/cancel")]
   TasksCancel(Value),
   ```
   Use `Value` for params to avoid the circular dependency. The params are parsed as `TaskGetParams`, `TaskResultParams`, `TaskListParams`, `TaskCancelParams` in core.rs.

4. **Update `ToolInfo::new()` and `ToolInfo::with_annotations()`** to initialize `execution: None`.

5. **Update `ToolInfo::with_ui()`** to initialize `execution: None`.

6. **Add a test** for the new ClientRequest variants confirming serde round-trip:
   ```rust
   #[test]
   fn test_task_client_request_variants() {
       // TasksGet
       let json_str = r#"{"method": "tasks/get", "params": {"taskId": "abc"}}"#;
       let req: ClientRequest = serde_json::from_str(json_str).unwrap();
       assert!(matches!(req, ClientRequest::TasksGet(_)));
       // Similar for TasksResult, TasksList, TasksCancel
   }
   ```

7. **Add a test** for CallToolRequest with task field:
   ```rust
   #[test]
   fn test_call_tool_request_with_task() {
       let json_str = r#"{"name": "my_tool", "arguments": {}, "task": {"ttl": 60000}}"#;
       let req: CallToolRequest = serde_json::from_str(json_str).unwrap();
       assert!(req.task.is_some());
   }
   ```

8. **Add a test** for ToolInfo with execution field.

IMPORTANT: The `ToolInfo` struct is `#[non_exhaustive]`, so adding a field is backward compatible. The `CallToolRequest` struct is NOT `#[non_exhaustive]` -- but the field is `Option` with `skip_serializing_if` and `serde(default)` which makes it backward compatible for deserialization (existing JSON without `task` field still works).

Add `#[serde(default)]` on the `task` field of `CallToolRequest` to handle existing JSON without the field.
  </action>
  <verify>
Run `cargo check --package pmcp` to verify compilation. Run `cargo test --package pmcp -- protocol::tests` to verify new and existing tests pass. Verify that existing `serialize_client_request` test still passes (adding enum variants should not break tagged enum deserialization of existing variants).
  </verify>
  <done>
CallToolRequest has optional task field, ToolInfo has optional execution field, ClientRequest has four task variants, all serde round-trip tests pass, existing tests unbroken.
  </done>
</task>

<task type="auto">
  <name>Task 2a: Define TaskRouter trait in pmcp</name>
  <files>src/server/tasks.rs, src/server/mod.rs</files>
  <action>
Create the `TaskRouter` trait in pmcp to provide the integration contract for task-enabled servers. This avoids circular dependency with pmcp-tasks by keeping the trait in the core crate.

1. **Create `src/server/tasks.rs`** with the TaskRouter trait:
   ```rust
   //! Task routing trait for MCP Tasks integration.
   //!
   //! This trait provides the integration point for task-enabled servers
   //! without creating a circular dependency with the pmcp-tasks crate.

   use async_trait::async_trait;
   use serde_json::Value;
   use crate::error::Result;

   /// Trait for routing MCP task requests.
   ///
   /// This trait is implemented by `pmcp-tasks` to handle task lifecycle
   /// operations without requiring `pmcp` to depend on `pmcp-tasks`.
   #[async_trait]
   pub trait TaskRouter: Send + Sync {
       /// Handle a task-augmented tools/call request.
       /// Returns CreateTaskResult as Value, or None if the tool does not support tasks.
       async fn handle_task_call(
           &self,
           tool_name: &str,
           arguments: Value,
           task_params: Value,
           owner_id: &str,
           progress_token: Option<Value>,
       ) -> Result<Value>;

       /// Handle tasks/get request.
       async fn handle_tasks_get(&self, params: Value, owner_id: &str) -> Result<Value>;

       /// Handle tasks/result request.
       async fn handle_tasks_result(&self, params: Value, owner_id: &str) -> Result<Value>;

       /// Handle tasks/list request.
       async fn handle_tasks_list(&self, params: Value, owner_id: &str) -> Result<Value>;

       /// Handle tasks/cancel request.
       async fn handle_tasks_cancel(&self, params: Value, owner_id: &str) -> Result<Value>;

       /// Resolve owner ID from authentication context fields.
       fn resolve_owner(
           &self,
           subject: Option<&str>,
           client_id: Option<&str>,
           session_id: Option<&str>,
       ) -> String;

       /// Check if a tool requires task augmentation (taskSupport: required).
       fn tool_requires_task(&self, tool_name: &str, tool_execution: Option<&Value>) -> bool;

       /// Get the server task capabilities as a Value for experimental.tasks.
       fn task_capabilities(&self) -> Value;
   }
   ```

2. **Update `src/server/mod.rs`** to include `pub mod tasks;` and re-export `TaskRouter`.
  </action>
  <verify>
Run `cargo check --package pmcp` to verify compilation. The trait has no implementors yet so this is a pure additive change.
  </verify>
  <done>
TaskRouter trait defined in src/server/tasks.rs. Re-exported from src/server/mod.rs. No circular dependencies. Compiles cleanly.
  </done>
</task>

<task type="auto">
  <name>Task 2b: Wire TaskRouter into ServerCoreBuilder and ServerCore</name>
  <files>src/server/builder.rs, src/server/core.rs</files>
  <action>
Plumb the TaskRouter trait through the builder and core, and update all call sites.

1. **Update `ServerCoreBuilder` in `src/server/builder.rs`:**
   - Add field: `task_router: Option<Arc<dyn TaskRouter>>`
   - Add method `with_task_store(mut self, router: Arc<dyn TaskRouter>) -> Self` that:
     - Stores the router
     - Auto-configures `experimental.tasks` capability: `let experimental = self.capabilities.experimental.get_or_insert_with(HashMap::new); experimental.insert("tasks".to_string(), router.task_capabilities());`
   - Initialize `task_router: None` in `new()`
   - Pass `self.task_router` to `ServerCore::new()` in `build()`

2. **Update `ServerCore` in `src/server/core.rs`:**
   - Add field: `task_router: Option<Arc<dyn TaskRouter>>`
   - Add parameter to `ServerCore::new()`: `task_router: Option<Arc<dyn TaskRouter>>`
   - Store it in the struct

3. **Update `ServerCapabilities`** in `src/types/capabilities.rs` if `experimental` field does not already exist as `Option<HashMap<String, Value>>`. Check -- research says it does exist.

4. **Update ALL existing `ServerCore::new()` call sites** to pass `task_router: None` for the new parameter. Explicitly enumerate every call site:
   - `src/server/builder.rs` — the `build()` method that constructs `ServerCore::new(...)`. Pass `self.task_router` (the builder field).
   - `src/server/core.rs` test helper ~line 787 — test `ServerCore::new()` call in `test_handle_request_*` helpers.
   - `src/server/core.rs` test ~line 835 — second test constructor.
   - `src/server/core.rs` test ~line 892 — third test constructor.
   - `src/server/core.rs` test ~line 940 — fourth test constructor.
   Search for ALL `ServerCore::new(` in the codebase (`grep -rn "ServerCore::new("`) and update every occurrence. If any call site is missed, compilation fails.

5. **Add builder test** verifying that `with_task_store` sets the experimental.tasks capability.

NOTE: The `with_task_store` method name is used per CONTEXT.md locked decision ("developer calls .with_task_store() on the server builder to enable tasks"), even though the parameter is `Arc<dyn TaskRouter>`. From the developer's perspective, they pass a store wrapped in a TaskRouter implementation that pmcp-tasks provides.
  </action>
  <verify>
Run `cargo check --package pmcp` to verify compilation. Run `cargo test --package pmcp` to verify all tests pass including existing core.rs and builder.rs tests. Verify that `ServerCoreBuilder::new().name("test").version("1.0.0").build()` still works (task_router defaults to None).
  </verify>
  <done>
ServerCoreBuilder has with_task_store() method that accepts Arc<dyn TaskRouter> and auto-configures experimental.tasks capability. ServerCore has task_router field. All existing tests pass with None for the new parameter.
  </done>
</task>

</tasks>

<verification>
1. `cargo check --package pmcp` compiles successfully
2. `cargo test --package pmcp` all tests pass
3. `cargo check --package pmcp-tasks` still compiles (no circular dep)
4. New ClientRequest variants parse from JSON-RPC method strings
5. CallToolRequest with task field deserializes correctly
6. ToolInfo with execution field serializes correctly
7. Builder with_task_store sets experimental.tasks capability
</verification>

<success_criteria>
- Protocol types extended with task fields and variants
- TaskRouter trait defined as the integration contract
- Builder accepts task router and auto-configures capabilities
- ServerCore stores task router
- Zero circular dependencies
- All existing tests still pass
</success_criteria>

<output>
After completion, create `.planning/phases/03-handler-middleware-and-server-integration/03-01-SUMMARY.md`
</output>
