feat: Adds close_heapsnapshot MCP tool (#2174)

This commit adds the close_heapsnapshot MCP tool such that the coding
agent can close heap snapshots again.

Co-authored-by: Dominik Inführ <dinfuehr@chromium.org>
Co-authored-by: Nicholas Roscino <nroscino@google.com>
This commit is contained in:
Dominik Inführ
2026-06-05 14:56:34 +02:00
committed by GitHub
parent dfc111502f
commit 8713b93b41
9 changed files with 128 additions and 3 deletions
+2 -1
View File
@@ -514,8 +514,9 @@ If you run into any issues, checkout our [troubleshooting guide](./docs/troubles
- [`take_snapshot`](docs/tool-reference.md#take_snapshot)
- [`screencast_start`](docs/tool-reference.md#screencast_start)
- [`screencast_stop`](docs/tool-reference.md#screencast_stop)
- **Memory** (5 tools)
- **Memory** (6 tools)
- [`take_heapsnapshot`](docs/tool-reference.md#take_heapsnapshot)
- [`close_heapsnapshot`](docs/tool-reference.md#close_heapsnapshot)
- [`get_heapsnapshot_class_nodes`](docs/tool-reference.md#get_heapsnapshot_class_nodes)
- [`get_heapsnapshot_details`](docs/tool-reference.md#get_heapsnapshot_details)
- [`get_heapsnapshot_retainers`](docs/tool-reference.md#get_heapsnapshot_retainers)
+12 -1
View File
@@ -39,8 +39,9 @@
- [`take_snapshot`](#take_snapshot)
- [`screencast_start`](#screencast_start)
- [`screencast_stop`](#screencast_stop)
- **[Memory](#memory)** (5 tools)
- **[Memory](#memory)** (6 tools)
- [`take_heapsnapshot`](#take_heapsnapshot)
- [`close_heapsnapshot`](#close_heapsnapshot)
- [`get_heapsnapshot_class_nodes`](#get_heapsnapshot_class_nodes)
- [`get_heapsnapshot_details`](#get_heapsnapshot_details)
- [`get_heapsnapshot_retainers`](#get_heapsnapshot_retainers)
@@ -454,6 +455,16 @@ in the DevTools Elements panel (if any).
---
### `close_heapsnapshot`
**Description:** Closes a previously loaded memory heapsnapshot, freeing its memory. (requires flag: --experimentalMemory=true)
**Parameters:**
- **filePath** (string) **(required)**: A path to the .heapsnapshot file to close.
---
### `get_heapsnapshot_class_nodes`
**Description:** Loads a memory heapsnapshot and returns instances of a specific class with their IDs. (requires flag: --experimentalMemory=true)
+7 -1
View File
@@ -201,12 +201,18 @@ export class HeapSnapshotManager {
return {snapshot, worker: workerProxy};
}
dispose(filePath: string): void {
hasSnapshots(): boolean {
return this.#snapshots.size > 0;
}
dispose(filePath: string): boolean {
const absolutePath = path.resolve(filePath);
const cached = this.#snapshots.get(absolutePath);
if (cached) {
cached.worker.dispose();
this.#snapshots.delete(absolutePath);
return true;
}
return false;
}
}
+8
View File
@@ -889,4 +889,12 @@ export class McpContext implements Context {
): Promise<DevTools.HeapSnapshotModel.HeapSnapshotModel.ItemsRange> {
return await this.#heapSnapshotManager.getRetainers(filePath, nodeId);
}
async closeHeapSnapshot(filePath: string): Promise<boolean> {
return this.#heapSnapshotManager.dispose(filePath);
}
hasHeapSnapshots(): boolean {
return this.#heapSnapshotManager.hasSnapshots();
}
}
+13
View File
@@ -81,6 +81,19 @@ export const commands: Commands = {
},
},
},
close_heapsnapshot: {
description:
'Closes a previously loaded memory heapsnapshot, freeing its memory. (requires flag: --experimentalMemory=true)',
category: 'Memory',
args: {
filePath: {
name: 'filePath',
type: 'string',
description: 'A path to the .heapsnapshot file to close.',
required: true,
},
},
},
close_page: {
description:
'Closes the page by its index. The last open page cannot be closed.',
+9
View File
@@ -738,5 +738,14 @@
"argType": "number"
}
]
},
{
"name": "close_heapsnapshot",
"args": [
{
"name": "file_path_length",
"argType": "number"
}
]
}
]
+1
View File
@@ -248,6 +248,7 @@ export type Context = Readonly<{
filePath: string,
nodeId: number,
): Promise<DevTools.HeapSnapshotModel.HeapSnapshotModel.ItemsRange>;
closeHeapSnapshot(filePath: string): Promise<boolean>;
}>;
/**
+29
View File
@@ -154,3 +154,32 @@ export const getHeapSnapshotRetainers = defineTool({
});
},
});
export const closeHeapSnapshot = defineTool({
name: 'close_heapsnapshot',
description:
'Closes a previously loaded memory heapsnapshot, freeing its memory.',
annotations: {
category: ToolCategory.MEMORY,
readOnlyHint: false,
conditions: ['experimentalMemory'],
},
verifyFilesSchema: ['filePath'],
schema: {
filePath: zod
.string()
.describe('A path to the .heapsnapshot file to close.'),
},
blockedByDialog: false,
handler: async (request, response, context) => {
const closed = await context.closeHeapSnapshot(request.params.filePath);
if (!closed) {
throw new Error(
`Failed to close heap snapshot: ${request.params.filePath} was not loaded.`,
);
}
response.appendResponseLine(
`Closed heap snapshot: ${request.params.filePath}`,
);
},
});
+47
View File
@@ -17,6 +17,7 @@ import {
getHeapSnapshotDetails,
getHeapSnapshotClassNodes,
getHeapSnapshotRetainers,
closeHeapSnapshot,
} from '../../src/tools/memory.js';
import {withMcpContext} from '../utils.js';
@@ -176,4 +177,50 @@ describe('memory', () => {
});
});
});
describe('close_heapsnapshot', () => {
it('with default options', async () => {
await withMcpContext(async (response, context) => {
const filePath = join(
process.cwd(),
'tests/fixtures/example.heapsnapshot',
);
await getHeapSnapshotSummary.handler(
{params: {filePath}},
response,
context,
);
assert.ok(context.hasHeapSnapshots());
await closeHeapSnapshot.handler(
{params: {filePath}},
response,
context,
);
assert.ok(
response.responseLines.includes(`Closed heap snapshot: ${filePath}`),
);
assert.ok(!context.hasHeapSnapshots());
});
});
it('with non-existent snapshot', async () => {
await withMcpContext(async (response, context) => {
const filePath = join(
process.cwd(),
'tests/fixtures/example.heapsnapshot',
);
await assert.rejects(
closeHeapSnapshot.handler({params: {filePath}}, response, context),
{
message: `Failed to close heap snapshot: ${filePath} was not loaded.`,
},
);
});
});
});
});