diff --git a/CHANGELOG.md b/CHANGELOG.md index dffc48e..75fe1ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,12 @@ - Dependency updates +# v1.5.3 - 2026-05-27 + +- Add destructive hints for tools. + # v1.5.2 - 2026-05-27 -- Add destructive hints for tools - Enable OpenAI connectors in OAuth # v1.5.0 - 2026-05-26 diff --git a/package-lock.json b/package-lock.json index b5c5c35..9ad05b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "browserless-mcp", - "version": "1.3.4", + "version": "1.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "browserless-mcp", - "version": "1.3.4", + "version": "1.5.3", "hasInstallScript": true, "license": "SSPL-1.0", "dependencies": { diff --git a/test/tools/annotations.spec.ts b/test/tools/annotations.spec.ts new file mode 100644 index 0000000..f7169cc --- /dev/null +++ b/test/tools/annotations.spec.ts @@ -0,0 +1,79 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { FastMCP } from 'fastmcp'; +import { registerSmartScraperTool } from '../../src/tools/smartscraper.js'; +import { registerFunctionTool } from '../../src/tools/function.js'; +import { registerDownloadTool } from '../../src/tools/download.js'; +import { registerExportTool } from '../../src/tools/export.js'; +import { registerAgentTools } from '../../src/tools/agent.js'; +import { registerSearchTool } from '../../src/tools/search.js'; +import { registerMapTool } from '../../src/tools/map.js'; +import { registerCrawlTool } from '../../src/tools/crawl.js'; +import { registerPerformanceTool } from '../../src/tools/performance.js'; +import type { McpConfig } from '../../src/@types/types.js'; + +const mockConfig: McpConfig = { + browserlessToken: 'test-token', + browserlessApiUrl: 'https://api.example.com', + transport: 'stdio', + port: 8080, + requestTimeout: 30000, + maxRetries: 0, + cacheTtlMs: 0, + analyticsEnabled: false, + sqsRegion: 'us-east-1', + oauthEnabled: false, + supabaseUrl: '', + supabaseOAuthClientId: '', + supabaseOAuthClientSecret: '', + supabaseServiceRoleKey: '', + mcpBaseUrl: '', + oauthAllowedRedirectUriPatterns: [], +}; + +// Every register function used by src/index.ts. registerAgentTools registers +// two tools (browserless_skill + browserless_agent); the rest register one. +const registrars = [ + registerSmartScraperTool, + registerFunctionTool, + registerDownloadTool, + registerExportTool, + registerAgentTools, + registerSearchTool, + registerMapTool, + registerCrawlTool, + registerPerformanceTool, +]; + +// MCP clients (notably OpenAI) reject any tool that does not set all three +// behavioural hints to an explicit boolean. fastmcp forwards `annotations` +// verbatim into tools/list, so the hint must live on every tool's annotations. +const REQUIRED_HINTS = [ + 'readOnlyHint', + 'destructiveHint', + 'openWorldHint', +] as const; + +describe('tool annotations', () => { + afterEach(() => sinon.restore()); + + it('every registered tool sets all three behavioural hints as booleans', () => { + const server = new FastMCP({ name: 'test', version: '0.1.0' }); + const addToolSpy = sinon.spy(server, 'addTool'); + + registrars.forEach((register) => register(server, mockConfig)); + + expect(addToolSpy.callCount).to.be.greaterThan(0); + + addToolSpy.getCalls().forEach((call) => { + const tool = call.args[0]; + const annotations = tool.annotations ?? {}; + REQUIRED_HINTS.forEach((hint) => { + expect( + annotations[hint], + `${tool.name} is missing ${hint} (must be an explicit boolean)`, + ).to.be.a('boolean'); + }); + }); + }); +});