Skip to content

Feature Request: mutate the active tool list on a live session (SDK re-triage of #735) #1896

Description

@ChrisKrawczyk

Feature Request: mutate the active tool list on a live session (SDK re-triage of #735)

Refiling per @patniko's invitation in #735 — 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 — 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

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) 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 Feature Request: session.setActiveTools() — filter visible tools without session teardown #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):

session.setActiveTools(names: string[]): Promise<void>;

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):

session.updateTools(tools: Tool[]): Promise<void>;

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)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions