`resumeSession` silently ignores the `mcpServers` config field
- Ngôn ngữ chính
- Java
- Star
- 10.5k
- Fork
- 1.5k
- Merge trung bình
- 1 ngày 11 giờ
- Pull request đã merge (30 ngày)
- 128
Mô tả
`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
Hướng dẫn đóng góp
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- javascript
- Lĩnh vực
- api
- Loại issue
- Lỗi
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức độ hoạt động
- Ít trao đổi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 68/100