-
Notifications
You must be signed in to change notification settings - Fork 55
Bug 2065545 - [firefox-devtools-mcp] Add a tool to disable the network cache #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2651620
d4066a9
92ab26d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /** | ||
| * Network cache behaviour (WebDriver BiDi network.setCacheBehavior) | ||
| */ | ||
|
|
||
| export type BiDiCommandFn = (method: string, params: Record<string, any>) => Promise<any>; | ||
|
|
||
| /** | ||
| * WebDriver BiDi network.CacheBehavior. | ||
| * - "default": normal HTTP cache behaviour | ||
| * - "bypass": skip the cache, so every request goes to the network | ||
| */ | ||
| export const CACHE_BEHAVIORS = ['default', 'bypass'] as const; | ||
|
|
||
| export type CacheBehavior = (typeof CACHE_BEHAVIORS)[number]; | ||
|
|
||
| export function isCacheBehavior(value: unknown): value is CacheBehavior { | ||
| return CACHE_BEHAVIORS.includes(value as CacheBehavior); | ||
| } | ||
|
|
||
| export class CacheManagement { | ||
| constructor( | ||
| private getCurrentContextId: () => string | null, | ||
| private sendBiDiCommand: BiDiCommandFn | ||
| ) {} | ||
|
|
||
| async setCacheBehavior(behavior: CacheBehavior, options?: { global?: boolean }): Promise<void> { | ||
| const params: Record<string, unknown> = { cacheBehavior: behavior }; | ||
|
|
||
| // Omitting `contexts` applies the behaviour globally; passing the current | ||
| // context scopes it to the selected tab, which is the default. | ||
| if (!options?.global) { | ||
| const contextId = this.getCurrentContextId(); | ||
| if (!contextId) { | ||
| throw new Error('Cannot set cache behavior: no browsing context ID'); | ||
| } | ||
| params.contexts = [contextId]; | ||
| } | ||
|
|
||
| await this.sendBiDiCommand('network.setCacheBehavior', params); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ import { saveOutput } from '../utils/save-output.js'; | |||||
| import { defineModule, defineToolHandler, type ToolDefinition } from './module.js'; | ||||||
| import type { McpToolResponse } from '../types/common.js'; | ||||||
| import type { NetworkBodyResult } from '../firefox/events/network.js'; | ||||||
| import { CACHE_BEHAVIORS, isCacheBehavior } from '../firefox/index.js'; | ||||||
|
|
||||||
| // Tool definitions | ||||||
| export const listNetworkRequestsTool = { | ||||||
|
|
@@ -131,6 +132,31 @@ export const getNetworkRequestTool = { | |||||
| }, | ||||||
| } satisfies ToolDefinition; | ||||||
|
|
||||||
| export const setNetworkCacheTool = { | ||||||
| name: 'set_network_cache', | ||||||
| description: | ||||||
| "Control the HTTP cache. Use behavior='bypass' so every request goes to the network — useful for performance measurement and for verifying a change that a cached asset would otherwise hide. Applies to the selected tab unless scope='global'. Persists until set back to 'default'.", | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Minor modifications for the description. Should make it clear that the behavior will not "move" to another tab when it gets selected. Also the MCP can restart Firefox and the behavior would be lost at this point. |
||||||
| annotations: { | ||||||
| readOnlyHint: false, | ||||||
| }, | ||||||
| inputSchema: { | ||||||
| type: 'object', | ||||||
| properties: { | ||||||
| behavior: { | ||||||
| type: 'string', | ||||||
| enum: [...CACHE_BEHAVIORS], | ||||||
| description: "'bypass' to skip the cache, 'default' to restore normal caching", | ||||||
| }, | ||||||
| scope: { | ||||||
| type: 'string', | ||||||
| enum: ['tab', 'global'], | ||||||
| description: "'tab' (default) applies to the selected tab; 'global' applies browser-wide", | ||||||
| }, | ||||||
| }, | ||||||
| required: ['behavior'], | ||||||
| }, | ||||||
| } satisfies ToolDefinition; | ||||||
|
|
||||||
| /** | ||||||
| * Fetch a body without letting a missing facade method or transport error fail | ||||||
| * the whole tool call. Absent support degrades to an 'unsupported' marker. | ||||||
|
|
@@ -576,11 +602,43 @@ export const handleGetNetworkRequest = defineToolHandler( | |||||
| } | ||||||
| ); | ||||||
|
|
||||||
| export async function handleSetNetworkCache(args: unknown): Promise<McpToolResponse> { | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: I can update it in a follow up but since PR #171, we can use defineToolHandler to avoid the duplicated try / catch + error handling |
||||||
| try { | ||||||
| const { behavior, scope } = (args as { behavior?: unknown; scope?: unknown }) || {}; | ||||||
|
|
||||||
| // An unrecognised value is rejected rather than defaulting: silently caching | ||||||
| // when the caller asked to bypass would quietly invalidate their measurement. | ||||||
| if (!isCacheBehavior(behavior)) { | ||||||
| return errorResponse( | ||||||
| `behavior must be one of ${CACHE_BEHAVIORS.join(', ')} (got ${JSON.stringify(behavior)})` | ||||||
| ); | ||||||
| } | ||||||
| if (scope !== undefined && scope !== 'tab' && scope !== 'global') { | ||||||
| return errorResponse(`scope must be 'tab' or 'global' (got ${JSON.stringify(scope)})`); | ||||||
| } | ||||||
|
|
||||||
| const isGlobal = scope === 'global'; | ||||||
|
|
||||||
| const { getFirefox } = await import('../index.js'); | ||||||
| const firefox = await getFirefox(); | ||||||
|
|
||||||
| await firefox.setCacheBehavior(behavior, { global: isGlobal }); | ||||||
|
|
||||||
| return successResponse( | ||||||
| `cache ${behavior} (${isGlobal ? 'all tabs' : 'selected tab'})` + | ||||||
| (behavior === 'bypass' ? " — set behavior='default' to restore caching" : '') | ||||||
| ); | ||||||
| } catch (error) { | ||||||
| return errorResponse(error instanceof Error ? error : new Error(String(error))); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| export const module = defineModule({ | ||||||
| name: 'network', | ||||||
| description: 'List and inspect network requests.', | ||||||
| description: 'List and inspect network requests, and control the HTTP cache.', | ||||||
| tools: [ | ||||||
| [listNetworkRequestsTool, handleListNetworkRequests], | ||||||
| [getNetworkRequestTool, handleGetNetworkRequest], | ||||||
| [setNetworkCacheTool, handleSetNetworkCache], | ||||||
| ], | ||||||
| }); | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about the backward compatibility issue?
src/toolsandsrc/firefoxshould always be in sync, sosrc/tools/network.tscan import fromcache.tsdirectly?