---
phase: 03-handler-middleware-and-server-integration
plan: 02
type: execute
wave: 2
depends_on: ["03-01"]
files_modified:
  - src/server/core.rs
  - crates/pmcp-tasks/src/lib.rs
  - crates/pmcp-tasks/src/router.rs
autonomous: true
requirements:
  - INTG-03
  - INTG-04
  - INTG-05
  - INTG-06
  - INTG-07
  - INTG-08
  - INTG-09
  - INTG-11
  - INTG-12

must_haves:
  truths:
    - "A tools/call request with a task field returns CreateTaskResult immediately and the handler is invoked in the background"
    - "A tools/call request to a tool with taskSupport: required auto-creates a task even without a task field"
    - "tasks/get returns current task state for a given task_id and owner"
    - "tasks/result returns the stored operation result for terminal tasks with related-task _meta"
    - "tasks/list returns paginated tasks scoped to the requesting owner"
    - "tasks/cancel transitions non-terminal tasks to cancelled status"
    - "A task created with ttl=5000 expires after 5 seconds and rejects further mutations"
    - "A tool call with progressToken stores it in task variables for external services to retrieve"
  artifacts:
    - path: "src/server/core.rs"
      provides: "Task routing in handle_request_internal for all four task endpoints and task-augmented tools/call interception"
      contains: "TasksGet"
    - path: "crates/pmcp-tasks/src/router.rs"
      provides: "TaskRouter implementation that bridges TaskStore to the pmcp TaskRouter trait"
      contains: "impl TaskRouter"
    - path: "crates/pmcp-tasks/src/lib.rs"
      provides: "Re-export of TaskRouterImpl and router module"
      contains: "pub mod router"
  key_links:
    - from: "src/server/core.rs"
      to: "crates/pmcp-tasks/src/router.rs"
      via: "TaskRouter trait (defined in pmcp, implemented in pmcp-tasks)"
      pattern: "task_router"
    - from: "crates/pmcp-tasks/src/router.rs"
      to: "crates/pmcp-tasks/src/store/mod.rs"
      via: "TaskStore operations for create/get/list/cancel/get_result"
      pattern: "task_store"
    - from: "src/server/core.rs"
      to: "src/server/auth/traits.rs"
      via: "AuthContext.subject and client_id for owner resolution"
      pattern: "resolve_owner"
---

<objective>
Wire task routing in ServerCore and implement TaskRouter in pmcp-tasks. This delivers the core integration: task-augmented tools/call interception, all four task endpoint handlers, owner resolution from auth context, TTL enforcement, and progressToken threading.

Purpose: Make task endpoints functional so the server can handle the full task lifecycle.
Output: Working task routing in core.rs, TaskRouter implementation in pmcp-tasks.
</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
@.planning/phases/03-handler-middleware-and-server-integration/03-01-SUMMARY.md
@src/server/core.rs
@src/server/cancellation.rs
@crates/pmcp-tasks/src/lib.rs
@crates/pmcp-tasks/src/store/mod.rs
@crates/pmcp-tasks/src/context.rs
@crates/pmcp-tasks/src/security.rs
</context>

<tasks>

<task type="auto">
  <name>Task 1: Implement TaskRouter in pmcp-tasks crate</name>
  <files>crates/pmcp-tasks/src/router.rs, crates/pmcp-tasks/src/lib.rs</files>
  <action>
Create `crates/pmcp-tasks/src/router.rs` implementing the `TaskRouter` trait from pmcp:

```rust
//! TaskRouter implementation bridging pmcp's TaskRouter trait to TaskStore operations.

use async_trait::async_trait;
use pmcp::server::tasks::TaskRouter;
use serde_json::Value;
use std::sync::Arc;

use crate::{
    TaskStore, TaskContext, TaskSecurityConfig, TaskError,
    TaskParams, TaskGetParams, TaskResultParams, TaskListParams, TaskCancelParams,
    CreateTaskResult, ServerTaskCapabilities,
    resolve_owner_id, related_task_meta,
    ListTasksOptions,
};

/// Implementation of pmcp's TaskRouter trait using a TaskStore backend.
///
/// This struct bridges the pmcp server's request routing to the pmcp-tasks
/// store operations. It owns the store and security config, and handles
/// all task lifecycle operations.
pub struct TaskRouterImpl {
    store: Arc<dyn TaskStore>,
    security_config: TaskSecurityConfig,
}
```

**Implement the struct:**

1. `TaskRouterImpl::new(store: Arc<dyn TaskStore>) -> Self` -- uses default security config
2. `TaskRouterImpl::with_security(store: Arc<dyn TaskStore>, config: TaskSecurityConfig) -> Self`
3. Expose a method to get the store for direct access: `pub fn store(&self) -> &Arc<dyn TaskStore>`

**Implement `TaskRouter` for `TaskRouterImpl`:**

1. **`handle_task_call`**: Parse `task_params` as `TaskParams`. Call `store.create(owner_id, "tools/call", task_params.ttl)`. Build `CreateTaskResult` from the created `TaskRecord`. Serialize to Value. The `arguments` and `tool_name` are passed through but NOT executed here -- execution is the server's responsibility (per CONTEXT.md: handler triggers external service, returns immediately). Store the tool_name and arguments as task variables so external services can pick them up. If `progress_token` is provided, store it as a task variable too.

2. **`handle_tasks_get`**: Parse params as `TaskGetParams`. Call `store.get(&params.task_id, owner_id)`. Convert `TaskRecord.task` to `GetTaskResult` (which is just `Task`). Serialize to Value.

3. **`handle_tasks_result`**: Parse params as `TaskResultParams`. Call `store.get_result(&params.task_id, owner_id)`. Wrap result with `related_task_meta(&params.task_id)` in `_meta`. Build a response object with the stored result value and `_meta`.

4. **`handle_tasks_list`**: Parse params as `TaskListParams`. Build `ListTasksOptions` from cursor. Call `store.list(owner_id, &options)`. Convert `TaskPage` to a JSON response with `tasks` array and optional `nextCursor`.

5. **`handle_tasks_cancel`**: Parse params as `TaskCancelParams`. Call `store.cancel(&params.task_id, owner_id)`. Convert result to `CancelTaskResult` (which is just `Task`). Serialize to Value.

6. **`resolve_owner`**: Delegate to `crate::resolve_owner_id(subject, client_id, session_id)`.

7. **`tool_requires_task`**: Parse the `tool_execution` Value as `ToolExecution`, check if `task_support == TaskSupport::Required`.

8. **`task_capabilities`**: Return `serde_json::to_value(ServerTaskCapabilities::full()).unwrap()`.

**Error handling**: Convert `TaskError` to `pmcp::error::Error` using the error code from `TaskError::error_code()` and the display message. Use `pmcp::error::Error::internal()` or a custom conversion.

**Update `crates/pmcp-tasks/src/lib.rs`:**
- Add `pub mod router;`
- Add `pub use router::TaskRouterImpl;`

**Update `crates/pmcp-tasks/Cargo.toml`** if any new features are needed (likely not -- pmcp is already a dependency).

IMPORTANT per CONTEXT.md locked decisions:
- The handler does NOT execute the tool. It creates the task, stores tool info in variables, and returns immediately.
- Background execution is external (Step Functions, SQS, etc.) or simulated with tokio::spawn in examples only.
- The TaskRouter's `handle_task_call` should store enough context in task variables for an external service to pick up the work: tool_name, arguments, progress_token.
  </action>
  <verify>
Run `cargo check --package pmcp-tasks` to verify compilation. Run `cargo test --package pmcp-tasks` to verify existing tests still pass.
  </verify>
  <done>
TaskRouterImpl in pmcp-tasks implements all TaskRouter trait methods, correctly delegates to TaskStore operations, handles owner resolution, stores tool context in task variables for external execution, and converts errors properly.
  </done>
</task>

<task type="auto">
  <name>Task 2: Wire task routing in ServerCore handle_request_internal</name>
  <files>src/server/core.rs</files>
  <action>
Modify `handle_request_internal` in `src/server/core.rs` to route task requests:

1. **Add helper method for owner resolution from auth context:**
   ```rust
   /// Resolve the owner ID from the authentication context using the task router.
   fn resolve_task_owner(&self, auth_context: &Option<AuthContext>) -> Option<String> {
       let router = self.task_router.as_ref()?;
       Some(match auth_context {
           Some(ctx) => router.resolve_owner(
               Some(&ctx.subject),
               ctx.client_id.as_deref(),
               None,
           ),
           None => router.resolve_owner(None, None, None),
       })
   }
   ```

2. **Add task-augmented tools/call interception** in the `ClientRequest::CallTool` match arm BEFORE the existing `handle_call_tool` call:
   ```rust
   ClientRequest::CallTool(req) => {
       // Check for task-augmented call
       if let Some(ref task_router) = self.task_router {
           let needs_task = req.task.is_some()
               || task_router.tool_requires_task(&req.name, req.execution.as_ref());
           if needs_task {
               let owner_id = self.resolve_task_owner(&auth_context)
                   .unwrap_or_else(|| "local".to_string());
               let task_params = req.task.clone().unwrap_or_else(|| serde_json::json!({}));
               let progress_token = req._meta.as_ref()
                   .and_then(|m| m.progress_token.as_ref())
                   .map(|t| serde_json::to_value(t).unwrap());
               match task_router.handle_task_call(
                   &req.name,
                   req.arguments.clone(),
                   task_params,
                   &owner_id,
                   progress_token,
               ).await {
                   Ok(result) => return Self::success_response(id, result),
                   Err(e) => return Self::error_response(id, -32603, e.to_string()),
               }
           }
       }
       // Normal tool call path (existing code)
       match self.handle_call_tool(req, auth_context.clone()).await {
           // ... existing code unchanged
       }
   }
   ```

   Note: For detecting tool_requires_task, we need the tool's execution metadata. The tool handler's `metadata()` returns `Option<ToolInfo>` which now has an `execution` field. Look up the handler, call `metadata()`, extract `execution`, pass to `tool_requires_task()`.

3. **Add match arms for four task endpoints** in the `match boxed_req.as_ref()` block:
   ```rust
   ClientRequest::TasksGet(params) => {
       if let Some(ref task_router) = self.task_router {
           let owner_id = self.resolve_task_owner(&auth_context)
               .unwrap_or_else(|| "local".to_string());
           match task_router.handle_tasks_get(params.clone(), &owner_id).await {
               Ok(result) => Self::success_response(id, result),
               Err(e) => Self::error_response(id, -32603, e.to_string()),
           }
       } else {
           Self::error_response(id, -32601, "Tasks not enabled".to_string())
       }
   },
   // Similar for TasksResult, TasksList, TasksCancel
   ```

4. **Preserve the existing `_ =>` catch-all** for unsupported methods.

5. **For INTG-11 (progressToken threading):** The progressToken is already available via `req._meta.progress_token`. The `handle_task_call` method receives it and stores it in task variables. No additional wiring needed in core.rs beyond extracting and passing it.

6. **For INTG-12 (model immediate response):** The CreateTaskResult returned by handle_task_call can include `_meta` with the model immediate response key. This is handled in the TaskRouterImpl, not in core.rs.

7. **For INTG-09 (TTL enforcement):** TTL from the task params is passed through to the store via handle_task_call. The store already handles TTL clamping via StoreConfig. No additional logic needed in core.rs.

IMPORTANT: Do NOT modify `handle_call_tool()` -- the task interception happens BEFORE it in `handle_request_internal`, returning `CreateTaskResult` directly as Value via `success_response`. This avoids the `CallToolResult` type mismatch issue identified in the research.
  </action>
  <verify>
Run `cargo check --package pmcp` to verify compilation. Run `cargo test --package pmcp` to verify all existing tests pass. The new code paths are not yet exercised by tests (that's Plan 03).
  </verify>
  <done>
ServerCore routes all four task endpoints through TaskRouter. Task-augmented tools/call is intercepted before normal tool execution. Owner resolution bridges AuthContext to TaskRouter. TTL and progressToken are passed through. All existing tests still pass.
  </done>
</task>

</tasks>

<verification>
1. `cargo check --package pmcp` compiles
2. `cargo check --package pmcp-tasks` compiles
3. `cargo test --package pmcp` passes all existing tests
4. `cargo test --package pmcp-tasks` passes all existing tests
5. Task endpoint variants are routed through TaskRouter
6. Task-augmented tools/call intercepted before normal handle_call_tool
7. Owner resolution works with and without auth context
</verification>

<success_criteria>
- TaskRouterImpl fully implements TaskRouter trait
- All four task endpoints routed in handle_request_internal
- Task-augmented tools/call creates task and returns CreateTaskResult
- Long-running tools (taskSupport: required) auto-create tasks
- Owner resolution from AuthContext works
- TTL and progressToken threaded through
- No regressions in existing tests
</success_criteria>

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