Feature Request: mutate the active tool list on a live session (SDK re-triage of #735)
- Langage dominant
- Java
- Étoiles
- 10.5k
- Forks
- 1.5k
- Merge moyen
- 1 j 11 h
- PR mergées (30 j)
- 127
Description
# Feature Request: mutate the active tool list on a live session (SDK re-triage of #735)
Refiling per @patniko's invitation in [#735](https://github.com/github/copilot-sdk/issues/735#issuecomment-4626625863) — the original closing comment specifically asked for fresh reproduction details from a current SDK user, and this is exactly that.
## Problem
`CopilotSession` today takes its custom tool list once at creation (`client.createSession({ tools })`) and again at `client.resumeSession({ tools })`. There is no supported way to mutate the active tool list on a **live session** without terminating conversation state at the SDK layer.
This blocks a well-defined UX pattern that MCP-integrating hosts consistently hit: **slow-auth MCP servers that connect after session creation**.
## Concrete production repro
**Host**: [`obsidian-copilot-agent`](https://github.com/ChrisKrawczyk/obsidian-copilot-agent) — an Obsidian community plugin embedding Copilot Agent, shipped v0.8.0.
**Scenario** (verified end-to-end):
1. User opens Obsidian → plugin loads → the plugin's `ConversationManager` creates a `CopilotSession` for the active conversation. Tool list at this point = built-ins + already-connected MCP servers only.
2. User has an MCP server configured that uses Device Flow / OAuth (Microsoft Graph via WorkIQ is the concrete case, but any slow-auth server exhibits this). This server has not finished authenticating yet.
3. 10–90 seconds later, the MCP server completes auth and connects. The plugin's `McpManager` fires a connect notification. New tools are discovered.
4. **Expected**: the user, mid-conversation, can ask the assistant to use the newly available tools.
5. **Actual**: the assistant reports it has no such tools. The tool list frozen at `createSession(...)` time is still what the CLI/model sees.
6. **Only workaround**: reload the plugin (loses the conversation) or switch conversations away and back (metadata-only conversations pick up the fresh tool list on their next `createSession(...)`, at the cost of a session recreation).
Same pattern shows up with:
- MCP servers that restart / crashloop (their tools should drop out for the duration).
- MCP servers added post-hoc via the settings UI while a conversation is open.
- Tool-schema changes on an already-connected server (`session.tools_updated` fires but the plugin has no way to act on it).
## Related prior issues
- **[#735](https://github.com/github/copilot-sdk/issues/735)** — *setActiveTools() — filter visible tools without session teardown.* Same underlying feature; closed as backlog trim, invited re-file.
- **[#1143](https://github.com/github/copilot-sdk/issues/1143)** — *session-scoped tool inventory API.* Adjacent read-side gap (list what's actually loaded); explicitly cross-references #735 as the write-side counterpart.
- **[#861](https://github.com/github/copilot-sdk/issues/861)** / **[#869](https://github.com/github/copilot-sdk/issues/869)** — MCP tool naming issues that hosts already work around, but the underlying wiring is the same code path a live-update method would touch.
## What we already did to isolate the SDK gap
To be sure this is an SDK gap and not a host bug, `obsidian-copilot-agent` v0.8.0 landed a full readiness/refresh flow *inside the plugin*:
- Watches `McpManager` state transitions with per-server 5s coalescing (proposal 0003 in-repo).
- Attempts to refresh the live session's tools via a feature-detect: `typeof session.updateTools === "function"`. With SDK `1.0.0` this always returns false, so we log a strict no-op ([FR-011 in our spec](https://github.com/ChrisKrawczyk/obsidian-copilot-agent/blob/main/.paw/work/mcp-readiness-ux/Spec.md)) and surface a "server connected" toast only when we can actually act.
- Surfaces an inline chat composer readiness pill so users at least know MCP is still coming up.
We deliberately did not attempt `resumeSession(...)` as a workaround because:
1. It creates a fresh session object; every event subscription (`session.on`, `session.onPermissionRequest`, canvas handlers, elicitation, tool handlers, hooks) has to be reattached. This is exactly the fragility #735 warned about — the Node SDK exhibits the same class of issue as the Python SDK originally reported, just with different specifics.
2. There is no documented guarantee that `resumeSession(sessionId, { tools })` preserves in-flight turn state or event ordering if called during `sendAndWait`.
3. Even at idle, doing a whole-object swap on every MCP transition is a lot of blast radius for what should be an in-place mutation.
So we hit the wall and shipped behind the no-op fallback.
## Requested API
Not prescriptive on shape — a maintainer decision — but a minimum viable surface would be one of:
**Option A** (mirrors #735 as originally proposed):
```ts
session.setActiveTools(names: string[]): Promise;
```
Filters *previously registered* tools by name for the next-turn context window. Simple, low-blast-radius, but requires the caller to register the full palette up front.
**Option B** (mirrors the existing `setModel` pattern for mid-session mutation):
```ts
session.updateTools(tools: Tool[]): Promise;
```
Replaces the declared list wholesale. More flexible for MCP integration where the tool set genuinely changes (server connect adds new tool *names*, not just a filter over a pre-known palette).
For MCP-integrating hosts, **B** is strictly more useful because we don't know the tool names ahead of time — they come from the MCP server's `tools/list` response, which the CLI executes internally.
### Semantics we would rely on (for either option)
- Effect timing: **next user turn onward**. In-flight turn's tool declarations are frozen — mid-stream calls do not affect them. This matches the plugin's existing turn-boundary latch and is the least-surprising behavior for the model.
- Ordering under concurrency: **last-committed wins**. Callers cannot reasonably serialize; the host already has to deal with rapid MCP flapping.
- Disconnected session: **rejects** with a documented error type. Do not silently no-op — hosts need to distinguish "SDK doesn't support this" from "session is dead".
- Conversation state: preserved (history, canvases, permission handlers, event subscribers). This is the entire point vs. `resumeSession`.
- Handler registration (Option B only): tool handlers registered via `defineTool` and passed in the new list should be dispatched by name on subsequent turns.
## Contribution offer
Happy to author the Node SDK side of the PR once the API shape is decided — the plugin-side integration is already coded behind a feature detect, so adoption is a version bump + swap-in.
## Environment
- `@github/copilot-sdk@1.0.0` (Node)
- Obsidian community plugin, Electron renderer process, `requestUrl`-routed MCP HTTP
- Windows / macOS / Linux verified
/cc @patniko (per #735 close-comment invitation)
Guide de contribution
Ouvrir le guide de contribution
Piste de recherche
Start at the Node SDK's CopilotSession createSession and resumeSession paths, then compare them with the existing setModel mutation and defineTool handler registration. Trace how tool declarations cross a turn boundary and how session state and subscriptions are retained; done means a maintainer-approved live-update API with documented concurrency, disconnected-session, and handler semantics.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- node.js, typescript
- Domaine
- api, tooling
- Type d'issue
- Fonctionnalité
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Calme
- Clarté
- Plutôt claire
- Accessibilité débutants
- 45/100