Merge pull request #3087 from code-yeongyu/fix/prepublish-mcp-regressions
fix: resolve MCP transcript cache and disable override regressions
This commit is contained in:
@@ -138,6 +138,41 @@ describe("getSystemMcpServerNames", () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("removes a server name when a higher-precedence config disables it", async () => {
|
||||||
|
// given
|
||||||
|
writeFileSync(join(TEST_HOME, ".claude.json"), JSON.stringify({
|
||||||
|
mcpServers: {
|
||||||
|
playwright: {
|
||||||
|
command: "npx",
|
||||||
|
args: ["@playwright/mcp@latest"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
writeFileSync(join(TEST_DIR, ".mcp.json"), JSON.stringify({
|
||||||
|
mcpServers: {
|
||||||
|
playwright: {
|
||||||
|
command: "npx",
|
||||||
|
args: ["@playwright/mcp@latest"],
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const originalCwd = process.cwd()
|
||||||
|
process.chdir(TEST_DIR)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// when
|
||||||
|
const { getSystemMcpServerNames } = await import("./loader")
|
||||||
|
const names = getSystemMcpServerNames()
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(names.has("playwright")).toBe(false)
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it("merges server names from multiple .mcp.json files", async () => {
|
it("merges server names from multiple .mcp.json files", async () => {
|
||||||
// given
|
// given
|
||||||
mkdirSync(join(TEST_DIR, ".claude"), { recursive: true })
|
mkdirSync(join(TEST_DIR, ".claude"), { recursive: true })
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ export function getSystemMcpServerNames(): Set<string> {
|
|||||||
if (!config?.mcpServers) continue
|
if (!config?.mcpServers) continue
|
||||||
|
|
||||||
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
||||||
if (serverConfig.disabled) continue
|
if (serverConfig.disabled) {
|
||||||
|
names.delete(name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (!shouldLoadMcpServer(serverConfig, cwd)) continue
|
if (!shouldLoadMcpServer(serverConfig, cwd)) continue
|
||||||
names.add(name)
|
names.add(name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,4 +99,82 @@ describe("transcript caching", () => {
|
|||||||
|
|
||||||
expect(client.session.messages).toHaveBeenCalledTimes(2)
|
expect(client.session.messages).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("keeps intermediate tool calls across sequential transcript rebuilds", async () => {
|
||||||
|
// given
|
||||||
|
const client = createMockClient([])
|
||||||
|
|
||||||
|
// when
|
||||||
|
const firstPath = await buildTranscriptFromSession(
|
||||||
|
client,
|
||||||
|
"ses_sequential",
|
||||||
|
"/tmp",
|
||||||
|
"bash",
|
||||||
|
{ command: "echo first" }
|
||||||
|
)
|
||||||
|
const secondPath = await buildTranscriptFromSession(
|
||||||
|
client,
|
||||||
|
"ses_sequential",
|
||||||
|
"/tmp",
|
||||||
|
"read",
|
||||||
|
{ filePath: "/tmp/second.txt" }
|
||||||
|
)
|
||||||
|
const thirdPath = await buildTranscriptFromSession(
|
||||||
|
client,
|
||||||
|
"ses_sequential",
|
||||||
|
"/tmp",
|
||||||
|
"write",
|
||||||
|
{ filePath: "/tmp/third.txt", content: "third" }
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(firstPath).not.toBeNull()
|
||||||
|
expect(secondPath).not.toBeNull()
|
||||||
|
expect(thirdPath).not.toBeNull()
|
||||||
|
|
||||||
|
if (thirdPath) {
|
||||||
|
const content = readFileSync(thirdPath, "utf-8")
|
||||||
|
|
||||||
|
expect(content).toContain("Bash")
|
||||||
|
expect(content).toContain("Read")
|
||||||
|
expect(content).toContain("Write")
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTempTranscript(firstPath)
|
||||||
|
deleteTempTranscript(secondPath)
|
||||||
|
deleteTempTranscript(thirdPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("cleans up previous temp transcript files when rebuilding cached transcripts", async () => {
|
||||||
|
// given
|
||||||
|
const client = createMockClient([])
|
||||||
|
|
||||||
|
// when
|
||||||
|
const firstPath = await buildTranscriptFromSession(
|
||||||
|
client,
|
||||||
|
"ses_cleanup",
|
||||||
|
"/tmp",
|
||||||
|
"bash",
|
||||||
|
{ command: "echo first" }
|
||||||
|
)
|
||||||
|
const secondPath = await buildTranscriptFromSession(
|
||||||
|
client,
|
||||||
|
"ses_cleanup",
|
||||||
|
"/tmp",
|
||||||
|
"read",
|
||||||
|
{ filePath: "/tmp/second.txt" }
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(firstPath).not.toBeNull()
|
||||||
|
expect(secondPath).not.toBeNull()
|
||||||
|
|
||||||
|
if (firstPath && secondPath) {
|
||||||
|
expect(existsSync(firstPath)).toBe(false)
|
||||||
|
expect(existsSync(secondPath)).toBe(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTempTranscript(firstPath)
|
||||||
|
deleteTempTranscript(secondPath)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -165,10 +165,12 @@ export async function buildTranscriptFromSession(
|
|||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
let baseEntries: string[]
|
let baseEntries: string[]
|
||||||
|
let previousTempPath: string | null = null
|
||||||
|
|
||||||
const cached = transcriptCache.get(sessionId)
|
const cached = transcriptCache.get(sessionId)
|
||||||
if (cached && isCacheValid(cached)) {
|
if (cached && isCacheValid(cached)) {
|
||||||
baseEntries = cached.baseEntries
|
baseEntries = cached.baseEntries
|
||||||
|
previousTempPath = cached.tempPath
|
||||||
} else {
|
} else {
|
||||||
// Fetch full session messages (only on first call or cache expiry)
|
// Fetch full session messages (only on first call or cache expiry)
|
||||||
const response = await client.session.messages({
|
const response = await client.session.messages({
|
||||||
@@ -199,6 +201,10 @@ export async function buildTranscriptFromSession(
|
|||||||
// Append current tool call
|
// Append current tool call
|
||||||
const allEntries = [...baseEntries, buildCurrentEntry(currentToolName, currentToolInput)]
|
const allEntries = [...baseEntries, buildCurrentEntry(currentToolName, currentToolInput)]
|
||||||
|
|
||||||
|
if (previousTempPath) {
|
||||||
|
try { unlinkSync(previousTempPath) } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
const tempPath = join(
|
const tempPath = join(
|
||||||
tmpdir(),
|
tmpdir(),
|
||||||
`opencode-transcript-${sessionId}-${randomUUID()}.jsonl`
|
`opencode-transcript-${sessionId}-${randomUUID()}.jsonl`
|
||||||
@@ -208,7 +214,9 @@ export async function buildTranscriptFromSession(
|
|||||||
// Update cache temp path for cleanup tracking
|
// Update cache temp path for cleanup tracking
|
||||||
const cacheEntry = transcriptCache.get(sessionId)
|
const cacheEntry = transcriptCache.get(sessionId)
|
||||||
if (cacheEntry) {
|
if (cacheEntry) {
|
||||||
|
cacheEntry.baseEntries = allEntries
|
||||||
cacheEntry.tempPath = tempPath
|
cacheEntry.tempPath = tempPath
|
||||||
|
cacheEntry.createdAt = Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
return tempPath
|
return tempPath
|
||||||
|
|||||||
Reference in New Issue
Block a user