feat: add session-configurable snapshot settings via browser_configure_snapshots

Implements dynamic snapshot configuration that MCP clients can control during
sessions without requiring server restarts or CLI changes.

New tool: browser_configure_snapshots
- Configure includeSnapshots, maxSnapshotTokens, differentialSnapshots at runtime
- Changes take effect immediately for subsequent tool calls
- Shows current settings when called with no parameters
- Provides helpful tips and usage guidance

Key improvements:
1. **Runtime Configuration**: Update snapshot behavior during active sessions
2. **Client Control**: MCP clients can adapt to different workflows dynamically
3. **Immediate Effect**: No server restart required - changes apply instantly
4. **State Tracking**: Context maintains current session configuration
5. **User Friendly**: Clear feedback on current settings and changes

Updated tool descriptions:
- All interactive tools now mention "configurable via browser_configure_snapshots"
- Removed references to CLI-only configuration
- Enhanced browser_snapshot description for explicit snapshots

Benefits for users:
🔄 Dynamic configuration without restarts
🎛️ Client-controlled snapshot behavior
📊 View current settings anytime
 Instant configuration changes
🎯 Adapt settings per workflow/task

Example usage:
```json
{
  "includeSnapshots": false,
  "maxSnapshotTokens": 25000,
  "differentialSnapshots": true
}
```

This transforms snapshot configuration from static CLI options into a flexible
session management system that adapts to client needs in real-time.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ryan Malloy 2025-08-22 08:28:36 -06:00
parent 574fdc4959
commit 2fe8b9355c
5 changed files with 110 additions and 8 deletions

View File

@ -636,4 +636,28 @@ export class Context {
this._lastSnapshotFingerprint = undefined; this._lastSnapshotFingerprint = undefined;
this._lastPageState = undefined; this._lastPageState = undefined;
} }
updateSnapshotConfig(updates: {
includeSnapshots?: boolean;
maxSnapshotTokens?: number;
differentialSnapshots?: boolean;
}): void {
// Update configuration at runtime
if (updates.includeSnapshots !== undefined)
(this.config as any).includeSnapshots = updates.includeSnapshots;
if (updates.maxSnapshotTokens !== undefined)
(this.config as any).maxSnapshotTokens = updates.maxSnapshotTokens;
if (updates.differentialSnapshots !== undefined) {
(this.config as any).differentialSnapshots = updates.differentialSnapshots;
// Reset differential state when toggling
if (updates.differentialSnapshots)
this.resetDifferentialSnapshot();
}
}
} }

View File

@ -74,6 +74,12 @@ const installPopularExtensionSchema = z.object({
version: z.string().optional().describe('Specific version to install (defaults to latest)') version: z.string().optional().describe('Specific version to install (defaults to latest)')
}); });
const configureSnapshotsSchema = z.object({
includeSnapshots: z.boolean().optional().describe('Enable/disable automatic snapshots after interactive operations. When false, use browser_snapshot for explicit snapshots.'),
maxSnapshotTokens: z.number().min(0).optional().describe('Maximum tokens allowed in snapshots before truncation. Use 0 to disable truncation.'),
differentialSnapshots: z.boolean().optional().describe('Enable differential snapshots that show only changes since last snapshot instead of full page snapshots.')
});
export default [ export default [
defineTool({ defineTool({
capability: 'core', capability: 'core',
@ -524,6 +530,78 @@ export default [
} }
}, },
}), }),
defineTool({
capability: 'core',
schema: {
name: 'browser_configure_snapshots',
title: 'Configure snapshot behavior',
description: 'Configure how page snapshots are handled during the session. Control automatic snapshots, size limits, and differential modes. Changes take effect immediately for subsequent tool calls.',
inputSchema: configureSnapshotsSchema,
type: 'destructive',
},
handle: async (context: Context, params: z.output<typeof configureSnapshotsSchema>, response: Response) => {
try {
const changes: string[] = [];
// Update snapshot configuration
if (params.includeSnapshots !== undefined)
changes.push(`📸 Auto-snapshots: ${params.includeSnapshots ? 'enabled' : 'disabled'}`);
if (params.maxSnapshotTokens !== undefined) {
if (params.maxSnapshotTokens === 0)
changes.push(`📏 Snapshot truncation: disabled (unlimited size)`);
else
changes.push(`📏 Max snapshot tokens: ${params.maxSnapshotTokens.toLocaleString()}`);
}
if (params.differentialSnapshots !== undefined) {
changes.push(`🔄 Differential snapshots: ${params.differentialSnapshots ? 'enabled' : 'disabled'}`);
if (params.differentialSnapshots)
changes.push(` ↳ Reset differential state for fresh tracking`);
}
// Apply the updated configuration using the context method
context.updateSnapshotConfig(params);
// Provide user feedback
if (changes.length === 0) {
response.addResult('No snapshot configuration changes specified.\n\n**Current settings:**\n' +
`📸 Auto-snapshots: ${context.config.includeSnapshots ? 'enabled' : 'disabled'}\n` +
`📏 Max snapshot tokens: ${context.config.maxSnapshotTokens === 0 ? 'unlimited' : context.config.maxSnapshotTokens.toLocaleString()}\n` +
`🔄 Differential snapshots: ${context.config.differentialSnapshots ? 'enabled' : 'disabled'}`);
return;
}
let result = '✅ **Snapshot configuration updated:**\n\n';
result += changes.map(change => `- ${change}`).join('\n');
result += '\n\n**💡 Tips:**\n';
if (!context.config.includeSnapshots)
result += '- Use `browser_snapshot` tool when you need explicit page snapshots\n';
if (context.config.differentialSnapshots) {
result += '- Differential mode shows only changes between operations\n';
result += '- First snapshot after enabling will establish baseline\n';
}
if (context.config.maxSnapshotTokens > 0 && context.config.maxSnapshotTokens < 5000)
result += '- Consider increasing token limit if snapshots are frequently truncated\n';
result += '\n**Changes take effect immediately for subsequent tool calls.**';
response.addResult(result);
} catch (error) {
throw new Error(`Failed to configure snapshots: ${error}`);
}
},
}),
]; ];
// Helper functions for extension downloading // Helper functions for extension downloading

View File

@ -27,7 +27,7 @@ const pressKey = defineTabTool({
schema: { schema: {
name: 'browser_press_key', name: 'browser_press_key',
title: 'Press a key', title: 'Press a key',
description: 'Press a key on the keyboard. Returns page snapshot after keypress unless disabled with --no-snapshots.', description: 'Press a key on the keyboard. Returns page snapshot after keypress (configurable via browser_configure_snapshots).',
inputSchema: z.object({ inputSchema: z.object({
key: z.string().describe('Name of the key to press or a character to generate, such as `ArrowLeft` or `a`'), key: z.string().describe('Name of the key to press or a character to generate, such as `ArrowLeft` or `a`'),
}), }),
@ -56,7 +56,7 @@ const type = defineTabTool({
schema: { schema: {
name: 'browser_type', name: 'browser_type',
title: 'Type text', title: 'Type text',
description: 'Type text into editable element. Returns page snapshot after typing unless disabled with --no-snapshots.', description: 'Type text into editable element. Returns page snapshot after typing (configurable via browser_configure_snapshots).',
inputSchema: typeSchema, inputSchema: typeSchema,
type: 'destructive', type: 'destructive',
}, },

View File

@ -23,7 +23,7 @@ const navigate = defineTool({
schema: { schema: {
name: 'browser_navigate', name: 'browser_navigate',
title: 'Navigate to a URL', title: 'Navigate to a URL',
description: 'Navigate to a URL. Returns page snapshot after navigation unless disabled with --no-snapshots.', description: 'Navigate to a URL. Returns page snapshot after navigation (configurable via browser_configure_snapshots).',
inputSchema: z.object({ inputSchema: z.object({
url: z.string().describe('The URL to navigate to'), url: z.string().describe('The URL to navigate to'),
}), }),

View File

@ -25,7 +25,7 @@ const snapshot = defineTool({
schema: { schema: {
name: 'browser_snapshot', name: 'browser_snapshot',
title: 'Page snapshot', title: 'Page snapshot',
description: 'Capture complete accessibility snapshot of the current page. Always returns full snapshot regardless of --no-snapshots or size limits. Better than screenshot for understanding page structure.', description: 'Capture complete accessibility snapshot of the current page. Always returns full snapshot regardless of session snapshot configuration. Better than screenshot for understanding page structure.',
inputSchema: z.object({}), inputSchema: z.object({}),
type: 'readOnly', type: 'readOnly',
}, },
@ -51,7 +51,7 @@ const click = defineTabTool({
schema: { schema: {
name: 'browser_click', name: 'browser_click',
title: 'Click', title: 'Click',
description: 'Perform click on a web page. Returns page snapshot after click unless disabled with --no-snapshots. Large snapshots (>10k tokens) are truncated - use browser_snapshot for full capture.', description: 'Perform click on a web page. Returns page snapshot after click (configurable via browser_configure_snapshots). Use browser_snapshot for explicit full snapshots.',
inputSchema: clickSchema, inputSchema: clickSchema,
type: 'destructive', type: 'destructive',
}, },
@ -85,7 +85,7 @@ const drag = defineTabTool({
schema: { schema: {
name: 'browser_drag', name: 'browser_drag',
title: 'Drag mouse', title: 'Drag mouse',
description: 'Perform drag and drop between two elements. Returns page snapshot after drag unless disabled with --no-snapshots.', description: 'Perform drag and drop between two elements. Returns page snapshot after drag (configurable via browser_configure_snapshots).',
inputSchema: z.object({ inputSchema: z.object({
startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'), startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'),
startRef: z.string().describe('Exact source element reference from the page snapshot'), startRef: z.string().describe('Exact source element reference from the page snapshot'),
@ -116,7 +116,7 @@ const hover = defineTabTool({
schema: { schema: {
name: 'browser_hover', name: 'browser_hover',
title: 'Hover mouse', title: 'Hover mouse',
description: 'Hover over element on page. Returns page snapshot after hover unless disabled with --no-snapshots.', description: 'Hover over element on page. Returns page snapshot after hover (configurable via browser_configure_snapshots).',
inputSchema: elementSchema, inputSchema: elementSchema,
type: 'readOnly', type: 'readOnly',
}, },
@ -142,7 +142,7 @@ const selectOption = defineTabTool({
schema: { schema: {
name: 'browser_select_option', name: 'browser_select_option',
title: 'Select option', title: 'Select option',
description: 'Select an option in a dropdown. Returns page snapshot after selection unless disabled with --no-snapshots.', description: 'Select an option in a dropdown. Returns page snapshot after selection (configurable via browser_configure_snapshots).',
inputSchema: selectOptionSchema, inputSchema: selectOptionSchema,
type: 'destructive', type: 'destructive',
}, },