`resumeSession` silently ignores the `mcpServers` config field
- 主要语言
- Java
- 星标
- 10.5k
- 派生
- 1.5k
- 平均合并
- 1 天 14 小时
- 30 天内合并 PR
- 129
描述
`CopilotClient.resumeSession(id, { mcpServers: ... })` does not apply the `mcpServers` value passed on resume. The servers connected during the
original `createSession` call remain connected; any servers declared on resume are never loaded. Other fields in `ResumeSessionConfig` (`tools`,
`skillDirectories`, `systemMessage`) *are* applied on the same call, so the behaviour is asymmetric.
### Reproduction
```js
import { CopilotClient } from '@github/copilot-sdk'
const baseConfig = {
model: 'gpt-4.1',
streaming: true,
onPermissionRequest: async () => ({ decision: 'allow', persist: false })
}
// Two different public MCP reference servers — distinct tool sets.
const mcpEverything = {
type: 'local',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-everything'],
tools: '*'
}
const mcpMemory = {
type: 'local',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-memory'],
tools: '*'
}
const LIST_PROMPT =
'Pick any MCP tool you actually have access to right now and call it ' +
'(any reasonable args). Then in one short sentence, name the tool you ' +
'just called and which MCP server it belongs to.'
async function ask (session, prompt) {
const response = await session.sendAndWait({ prompt }, 60_000)
return response?.data?.content ?? ''
}
const client = new CopilotClient({ autoStart: true })
await client.start()
const logServers = (phase) => (event) => {
if (event.type === 'session.mcp_servers_loaded') {
const names = (event.data?.servers ?? []).map(s => s.name)
console.log(` [SDK event in ${phase}] session.mcp_servers_loaded → ${JSON.stringify(names)}`)
}
}
console.log('\n=== 1. createSession with mcpServers = { everything } ===')
const sessionA = await client.createSession({
...baseConfig,
mcpServers: { everything: mcpEverything }
})
sessionA.on(logServers('createSession'))
console.log(' LLM reply: ' + await ask(sessionA, LIST_PROMPT))
console.log('\n=== 2. resumeSession(sameId, mcpServers = { memory }) ===')
const sessionB = await client.resumeSession(sessionA.id ?? sessionA.sessionId, {
...baseConfig,
mcpServers: { memory: mcpMemory } // ← DIFFERENT server
})
sessionB.on(logServers('resumeSession'))
console.log(' LLM reply: ' + await ask(sessionB, LIST_PROMPT))
await client.disconnect?.()
process.exit(0)
```
#### Run with:
```
npm i @github/copilot-sdk
node repro.mjs
```
(Node 22, `npx` on PATH, and a logged-in Copilot CLI — run
`./node_modules/.bin/copilot login` once.)
### Observed behavior
```
=== 1. createSession with mcpServers = { everything } ===
[SDK event in createSession] session.mcp_servers_loaded → ["everything"]
LLM reply: I called the get-sum tool from the "everything" MCP server.
=== 2. resumeSession(sameId, mcpServers = { memory }) ===
[SDK event in resumeSession] session.mcp_servers_loaded → ["everything"]
LLM reply: I called the get-tiny-image tool from the "everything" MCP server.
```
After step 2 the SDK is still publishing `["everything"]` in `session.mcp_servers_loaded`, and the LLM can only call tools from `everything` — even though the resume config explicitly passed `{ memory: ... }` and did **not** include `everything`.
### Expected behavior
After `resumeSession(id, { mcpServers: { memory: ... } })`:
- `session.mcp_servers_loaded` should emit `["memory"]`.
- The previously-loaded `everything` server should be disconnected (or at least no longer be visible as a tool source).
- The LLM should only be able to call tools from `memory` (e.g. `read_graph`, `create_entities`).
### Impact
Prevents agent-switching mid-conversation (same use case as #567). Only workaround is `deleteSession` + `createSession` — which wipes history, defeating the point of `resumeSession`.
### Environment
- `@github/copilot-sdk` 0.2.1 and 0.2.2 (both affected)
- CLI binary 1.0.34, Node 22.14, Linux
贡献指南
调研方向
Start at the CopilotClient.resumeSession entry point and compare its mcpServers handling with createSession, using the provided reproduction and session.mcp_servers_loaded event as checks. Done means resuming with { memory: ... } emits only ["memory"], disconnects or hides the previous everything server, and exposes only the replacement server's tools.
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- javascript
- 领域
- api
- Issue 类型
- 缺陷
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 活跃度
- 冷清
- 描述清晰度
- 描述清楚
- 新手友好度
- 68/100