mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-17 02:25:57 +00:00
chore: Merge branch 'main'
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { z } from 'zod'
|
||||
import { BookmarkAdapter } from '@/adapters/BookmarkAdapter'
|
||||
import { ActionHandler } from '../ActionHandler'
|
||||
|
||||
// Input schema
|
||||
const UpdateBookmarkInputSchema = z.object({
|
||||
id: z.string().describe('Bookmark ID to update'),
|
||||
title: z.string().optional().describe('New bookmark title'),
|
||||
url: z.string().url().optional().describe('New bookmark URL'),
|
||||
})
|
||||
|
||||
// Output schema
|
||||
const UpdateBookmarkOutputSchema = z.object({
|
||||
id: z.string().describe('Bookmark ID'),
|
||||
title: z.string().describe('Updated bookmark title'),
|
||||
url: z.string().optional().describe('Updated bookmark URL'),
|
||||
})
|
||||
|
||||
type UpdateBookmarkInput = z.infer<typeof UpdateBookmarkInputSchema>
|
||||
type UpdateBookmarkOutput = z.infer<typeof UpdateBookmarkOutputSchema>
|
||||
|
||||
/**
|
||||
* UpdateBookmarkAction - Update a bookmark's title or URL
|
||||
*
|
||||
* Updates an existing bookmark with new title and/or URL.
|
||||
*
|
||||
* Input:
|
||||
* - id: Bookmark ID to update
|
||||
* - title (optional): New title for the bookmark
|
||||
* - url (optional): New URL for the bookmark
|
||||
*
|
||||
* Output:
|
||||
* - id: Bookmark ID
|
||||
* - title: Updated title
|
||||
* - url: Updated URL
|
||||
*
|
||||
* Usage:
|
||||
* Update a bookmark's title or URL (at least one must be provided).
|
||||
*
|
||||
* Example:
|
||||
* {
|
||||
* "id": "123",
|
||||
* "title": "New Title",
|
||||
* "url": "https://www.example.com"
|
||||
* }
|
||||
* // Returns: { id: "123", title: "New Title", url: "https://www.example.com" }
|
||||
*/
|
||||
export class UpdateBookmarkAction extends ActionHandler<
|
||||
UpdateBookmarkInput,
|
||||
UpdateBookmarkOutput
|
||||
> {
|
||||
readonly inputSchema = UpdateBookmarkInputSchema
|
||||
private bookmarkAdapter = new BookmarkAdapter()
|
||||
|
||||
async execute(input: UpdateBookmarkInput): Promise<UpdateBookmarkOutput> {
|
||||
const changes: { title?: string; url?: string } = {}
|
||||
|
||||
if (input.title !== undefined) {
|
||||
changes.title = input.title
|
||||
}
|
||||
if (input.url !== undefined) {
|
||||
changes.url = input.url
|
||||
}
|
||||
|
||||
if (Object.keys(changes).length === 0) {
|
||||
throw new Error('At least one of title or url must be provided')
|
||||
}
|
||||
|
||||
const updated = await this.bookmarkAdapter.updateBookmark(input.id, changes)
|
||||
|
||||
return {
|
||||
id: updated.id,
|
||||
title: updated.title,
|
||||
url: updated.url,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { GetBookmarksAction } from '@/actions/bookmark/GetBookmarksAction'
|
||||
import { MoveBookmarkAction } from '@/actions/bookmark/MoveBookmarkAction'
|
||||
import { RemoveBookmarkAction } from '@/actions/bookmark/RemoveBookmarkAction'
|
||||
import { RemoveBookmarkTreeAction } from '@/actions/bookmark/RemoveBookmarkTreeAction'
|
||||
import { UpdateBookmarkAction } from '@/actions/bookmark/UpdateBookmarkAction'
|
||||
import { CaptureScreenshotAction } from '@/actions/browser/CaptureScreenshotAction'
|
||||
import { CaptureScreenshotPointerAction } from '@/actions/browser/CaptureScreenshotPointerAction'
|
||||
import { ClearAction } from '@/actions/browser/ClearAction'
|
||||
@@ -204,6 +205,7 @@ export class BrowserOSController {
|
||||
this.actionRegistry.register('getBookmarks', new GetBookmarksAction())
|
||||
this.actionRegistry.register('createBookmark', new CreateBookmarkAction())
|
||||
this.actionRegistry.register('removeBookmark', new RemoveBookmarkAction())
|
||||
this.actionRegistry.register('updateBookmark', new UpdateBookmarkAction())
|
||||
this.actionRegistry.register(
|
||||
'createBookmarkFolder',
|
||||
new CreateBookmarkFolderAction(),
|
||||
|
||||
@@ -135,10 +135,25 @@ When user asks to "organize tabs", "group tabs", or "clean up tabs":
|
||||
|
||||
Use when built-in tools cannot accomplish the task.
|
||||
|
||||
## Bookmarks & History
|
||||
- \`browser_get_bookmarks(folderId?)\` - Get bookmarks
|
||||
- \`browser_create_bookmark(title, url, parentId?)\` - Create bookmark
|
||||
## Bookmarks
|
||||
- \`browser_get_bookmarks(folderId?)\` - Get all bookmarks or from specific folder
|
||||
- \`browser_create_bookmark(title, url, parentId?)\` - Create bookmark (use parentId to place in folder)
|
||||
- \`browser_update_bookmark(bookmarkId, title?, url?)\` - Edit bookmark title or URL
|
||||
- \`browser_remove_bookmark(bookmarkId)\` - Delete bookmark
|
||||
- \`browser_create_bookmark_folder(title, parentId?)\` - Create folder (returns folderId to use as parentId)
|
||||
- \`browser_get_bookmark_children(folderId)\` - Get contents of a folder
|
||||
- \`browser_move_bookmark(bookmarkId, parentId?, index?)\` - Move bookmark or folder to new location
|
||||
- \`browser_remove_bookmark_tree(folderId, confirm)\` - Delete folder and all contents
|
||||
|
||||
**Organizing bookmarks into folders:**
|
||||
\`\`\`
|
||||
1. browser_create_bookmark_folder("Work") → folderId: "123"
|
||||
2. browser_create_bookmark("Docs", "https://docs.google.com", parentId="123")
|
||||
3. browser_move_bookmark(existingBookmarkId, parentId="123")
|
||||
\`\`\`
|
||||
Use \`browser_get_bookmarks\` to find existing folder IDs, or create new folders with \`browser_create_bookmark_folder\`.
|
||||
|
||||
## History
|
||||
- \`browser_search_history(query, maxResults?)\` - Search history
|
||||
- \`browser_get_recent_history(count?)\` - Recent history
|
||||
|
||||
|
||||
18
apps/server/src/api/routes/shutdown.ts
Normal file
18
apps/server/src/api/routes/shutdown.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono'
|
||||
|
||||
interface ShutdownRouteConfig {
|
||||
onShutdown: () => void
|
||||
}
|
||||
|
||||
export function createShutdownRoute(config: ShutdownRouteConfig) {
|
||||
return new Hono().post('/', (c) => {
|
||||
setImmediate(config.onShutdown)
|
||||
return c.json({ status: 'ok' })
|
||||
})
|
||||
}
|
||||
@@ -7,11 +7,11 @@
|
||||
import { Hono } from 'hono'
|
||||
import type { ControllerContext } from '../../browser/extension/context'
|
||||
|
||||
interface ExtensionStatusDeps {
|
||||
interface StatusDeps {
|
||||
controllerContext: ControllerContext
|
||||
}
|
||||
|
||||
export function createExtensionStatusRoute(deps: ExtensionStatusDeps) {
|
||||
export function createStatusRoute(deps: StatusDeps) {
|
||||
const { controllerContext } = deps
|
||||
|
||||
return new Hono().get('/', (c) =>
|
||||
@@ -17,13 +17,14 @@ import { HttpAgentError } from '../agent/errors'
|
||||
import { logger } from '../lib/logger'
|
||||
import { bindPortWithRetry } from '../lib/port-binding'
|
||||
import { createChatRoutes } from './routes/chat'
|
||||
import { createExtensionStatusRoute } from './routes/extension-status'
|
||||
import { createGraphRoutes } from './routes/graph'
|
||||
import { createHealthRoute } from './routes/health'
|
||||
import { createKlavisRoutes } from './routes/klavis'
|
||||
import { createMcpRoutes } from './routes/mcp'
|
||||
import { createProviderRoutes } from './routes/provider'
|
||||
import { createSdkRoutes } from './routes/sdk'
|
||||
import { createShutdownRoute } from './routes/shutdown'
|
||||
import { createStatusRoute } from './routes/status'
|
||||
import type { Env, HttpServerConfig } from './types'
|
||||
import { defaultCorsConfig } from './utils/cors'
|
||||
|
||||
@@ -49,16 +50,17 @@ export async function createHttpServer(config: HttpServerConfig) {
|
||||
allowRemote,
|
||||
} = config
|
||||
|
||||
const { healthWatchdog } = config
|
||||
const { healthWatchdog, onShutdown } = config
|
||||
|
||||
// DECLARATIVE route composition - chain .route() calls for type inference
|
||||
const app = new Hono<Env>()
|
||||
.use('/*', cors(defaultCorsConfig))
|
||||
.route('/health', createHealthRoute({ watchdog: healthWatchdog }))
|
||||
.route(
|
||||
'/extension-status',
|
||||
createExtensionStatusRoute({ controllerContext }),
|
||||
'/shutdown',
|
||||
createShutdownRoute({ onShutdown: onShutdown ?? (() => {}) }),
|
||||
)
|
||||
.route('/status', createStatusRoute({ controllerContext }))
|
||||
.route('/test-provider', createProviderRoutes())
|
||||
.route('/klavis', createKlavisRoutes({ browserosId: browserosId || '' }))
|
||||
.route(
|
||||
|
||||
@@ -84,6 +84,9 @@ export interface HttpServerConfig {
|
||||
|
||||
// For health monitoring
|
||||
healthWatchdog?: HealthWatchdog
|
||||
|
||||
// For shutdown route
|
||||
onShutdown?: () => void
|
||||
}
|
||||
|
||||
// Graph request schemas
|
||||
|
||||
@@ -93,6 +93,7 @@ export class Application {
|
||||
rateLimiter: new RateLimiter(this.getDb(), dailyRateLimit),
|
||||
codegenServiceUrl: this.config.codegenServiceUrl,
|
||||
healthWatchdog: this.healthWatchdog ?? undefined,
|
||||
onShutdown: () => this.stop(),
|
||||
})
|
||||
} catch (error) {
|
||||
this.handleStartupError('HTTP server', this.config.serverPort, error)
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
moveBookmark,
|
||||
removeBookmark,
|
||||
removeBookmarkTree,
|
||||
updateBookmark,
|
||||
} from './tools/bookmarks'
|
||||
// Content Extraction
|
||||
export { getPageContent } from './tools/content'
|
||||
@@ -77,6 +78,7 @@ import {
|
||||
moveBookmark,
|
||||
removeBookmark,
|
||||
removeBookmarkTree,
|
||||
updateBookmark,
|
||||
} from './tools/bookmarks'
|
||||
import { getPageContent } from './tools/content'
|
||||
import { clickCoordinates, typeAtCoordinates } from './tools/coordinates'
|
||||
@@ -106,7 +108,7 @@ import {
|
||||
} from './tools/tab-management'
|
||||
import { createWindow } from './tools/window-management'
|
||||
|
||||
// Array export for convenience (36 tools total)
|
||||
// Array export for convenience (37 tools total)
|
||||
export const allControllerTools = [
|
||||
getActiveTab,
|
||||
listTabs,
|
||||
@@ -142,6 +144,7 @@ export const allControllerTools = [
|
||||
getBookmarkChildren,
|
||||
moveBookmark,
|
||||
removeBookmarkTree,
|
||||
updateBookmark,
|
||||
searchHistory,
|
||||
getRecentHistory,
|
||||
createWindow,
|
||||
|
||||
@@ -60,7 +60,8 @@ export const getBookmarks = defineTool<z.ZodRawShape, Context, Response>({
|
||||
|
||||
export const createBookmark = defineTool<z.ZodRawShape, Context, Response>({
|
||||
name: 'browser_create_bookmark',
|
||||
description: 'Create a new bookmark',
|
||||
description:
|
||||
'Create a new bookmark. Use parentId to place it inside an existing folder or a newly created one.',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: false,
|
||||
@@ -68,7 +69,12 @@ export const createBookmark = defineTool<z.ZodRawShape, Context, Response>({
|
||||
schema: {
|
||||
title: z.string().describe('Bookmark title'),
|
||||
url: z.string().describe('URL to bookmark'),
|
||||
parentId: z.string().optional().describe('Optional parent folder ID'),
|
||||
parentId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Folder ID to create bookmark in (from browser_get_bookmarks or browser_create_bookmark_folder)',
|
||||
),
|
||||
windowId: z.number().optional().describe('Window ID for routing'),
|
||||
},
|
||||
handler: async (request, response, context) => {
|
||||
@@ -116,13 +122,51 @@ export const removeBookmark = defineTool<z.ZodRawShape, Context, Response>({
|
||||
},
|
||||
})
|
||||
|
||||
export const updateBookmark = defineTool<z.ZodRawShape, Context, Response>({
|
||||
name: 'browser_update_bookmark',
|
||||
description: 'Update a bookmark title or URL',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: false,
|
||||
},
|
||||
schema: {
|
||||
bookmarkId: z.string().describe('Bookmark ID to update'),
|
||||
title: z.string().optional().describe('New title for the bookmark'),
|
||||
url: z.string().url().optional().describe('New URL for the bookmark'),
|
||||
windowId: z.number().optional().describe('Window ID for routing'),
|
||||
},
|
||||
handler: async (request, response, context) => {
|
||||
const { bookmarkId, title, url, windowId } = request.params as {
|
||||
bookmarkId: string
|
||||
title?: string
|
||||
url?: string
|
||||
windowId?: number
|
||||
}
|
||||
|
||||
const result = await context.executeAction('updateBookmark', {
|
||||
id: bookmarkId,
|
||||
title,
|
||||
url,
|
||||
windowId,
|
||||
})
|
||||
const data = result as { id: string; title: string; url?: string }
|
||||
|
||||
response.appendResponseLine(`Updated bookmark: ${data.title}`)
|
||||
if (data.url) {
|
||||
response.appendResponseLine(`URL: ${data.url}`)
|
||||
}
|
||||
response.appendResponseLine(`ID: ${data.id}`)
|
||||
},
|
||||
})
|
||||
|
||||
export const createBookmarkFolder = defineTool<
|
||||
z.ZodRawShape,
|
||||
Context,
|
||||
Response
|
||||
>({
|
||||
name: 'browser_create_bookmark_folder',
|
||||
description: 'Create a new bookmark folder',
|
||||
description:
|
||||
'Create a new bookmark folder. Returns folderId to use as parentId when creating or moving bookmarks into this folder.',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: false,
|
||||
@@ -214,14 +258,20 @@ export const getBookmarkChildren = defineTool<z.ZodRawShape, Context, Response>(
|
||||
|
||||
export const moveBookmark = defineTool<z.ZodRawShape, Context, Response>({
|
||||
name: 'browser_move_bookmark',
|
||||
description: 'Move a bookmark or folder to a new location',
|
||||
description:
|
||||
'Move a bookmark or folder into a different folder (existing or newly created).',
|
||||
annotations: {
|
||||
category: ToolCategories.BOOKMARKS,
|
||||
readOnlyHint: false,
|
||||
},
|
||||
schema: {
|
||||
bookmarkId: z.string().describe('Bookmark or folder ID to move'),
|
||||
parentId: z.string().optional().describe('New parent folder ID'),
|
||||
parentId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Destination folder ID (from browser_get_bookmarks or browser_create_bookmark_folder)',
|
||||
),
|
||||
index: z
|
||||
.number()
|
||||
.int()
|
||||
|
||||
@@ -44,7 +44,7 @@ const DEFAULT_BINARY_PATH =
|
||||
|
||||
async function isExtensionConnected(port: number): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/extension-status`, {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/status`, {
|
||||
signal: AbortSignal.timeout(1000),
|
||||
})
|
||||
if (response.ok) {
|
||||
|
||||
@@ -68,9 +68,9 @@ describe('HTTP Server Integration Tests', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Extension status endpoint', () => {
|
||||
describe('Status endpoint', () => {
|
||||
it('reports extension as connected', async () => {
|
||||
const response = await fetch(`${getBaseUrl()}/extension-status`)
|
||||
const response = await fetch(`${getBaseUrl()}/status`)
|
||||
assert.strictEqual(response.status, 200)
|
||||
|
||||
const json = (await response.json()) as {
|
||||
|
||||
@@ -366,6 +366,190 @@ describe('MCP Controller Bookmark Tools', () => {
|
||||
}, 30000)
|
||||
})
|
||||
|
||||
describe('browser_update_bookmark - Success Cases', () => {
|
||||
it('tests that updating bookmark title succeeds', async () => {
|
||||
await withMcpServer(async (client) => {
|
||||
// First create a bookmark
|
||||
const createResult = await client.callTool({
|
||||
name: 'browser_create_bookmark',
|
||||
arguments: {
|
||||
title: 'Original Title',
|
||||
url: 'https://update.example.com',
|
||||
},
|
||||
})
|
||||
|
||||
const createText = createResult.content.find((c) => c.type === 'text')
|
||||
const idMatch = createText.text.match(/ID: (\d+)/)
|
||||
const bookmarkId = idMatch ? idMatch[1] : '1'
|
||||
|
||||
// Update the title
|
||||
const result = await client.callTool({
|
||||
name: 'browser_update_bookmark',
|
||||
arguments: {
|
||||
bookmarkId,
|
||||
title: 'Updated Title',
|
||||
},
|
||||
})
|
||||
|
||||
console.log('\n=== Update Bookmark Title Response ===')
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
|
||||
assert.ok(!result.isError, 'Should succeed')
|
||||
|
||||
const textContent = result.content.find((c) => c.type === 'text')
|
||||
assert.ok(textContent, 'Should have text content')
|
||||
assert.ok(
|
||||
textContent.text.includes('Updated bookmark'),
|
||||
'Should confirm update',
|
||||
)
|
||||
assert.ok(
|
||||
textContent.text.includes('Updated Title'),
|
||||
'Should include new title',
|
||||
)
|
||||
|
||||
// Cleanup
|
||||
await client.callTool({
|
||||
name: 'browser_remove_bookmark',
|
||||
arguments: { bookmarkId },
|
||||
})
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
it('tests that updating bookmark URL succeeds', async () => {
|
||||
await withMcpServer(async (client) => {
|
||||
// First create a bookmark
|
||||
const createResult = await client.callTool({
|
||||
name: 'browser_create_bookmark',
|
||||
arguments: {
|
||||
title: 'URL Test',
|
||||
url: 'https://old-url.example.com',
|
||||
},
|
||||
})
|
||||
|
||||
const createText = createResult.content.find((c) => c.type === 'text')
|
||||
const idMatch = createText.text.match(/ID: (\d+)/)
|
||||
const bookmarkId = idMatch ? idMatch[1] : '1'
|
||||
|
||||
// Update the URL
|
||||
const result = await client.callTool({
|
||||
name: 'browser_update_bookmark',
|
||||
arguments: {
|
||||
bookmarkId,
|
||||
url: 'https://new-url.example.com',
|
||||
},
|
||||
})
|
||||
|
||||
console.log('\n=== Update Bookmark URL Response ===')
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
|
||||
assert.ok(!result.isError, 'Should succeed')
|
||||
|
||||
const textContent = result.content.find((c) => c.type === 'text')
|
||||
assert.ok(textContent, 'Should have text content')
|
||||
assert.ok(
|
||||
textContent.text.includes('https://new-url.example.com'),
|
||||
'Should include new URL',
|
||||
)
|
||||
|
||||
// Cleanup
|
||||
await client.callTool({
|
||||
name: 'browser_remove_bookmark',
|
||||
arguments: { bookmarkId },
|
||||
})
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
it('tests that updating both title and URL succeeds', async () => {
|
||||
await withMcpServer(async (client) => {
|
||||
// First create a bookmark
|
||||
const createResult = await client.callTool({
|
||||
name: 'browser_create_bookmark',
|
||||
arguments: {
|
||||
title: 'Original',
|
||||
url: 'https://original.example.com',
|
||||
},
|
||||
})
|
||||
|
||||
const createText = createResult.content.find((c) => c.type === 'text')
|
||||
const idMatch = createText.text.match(/ID: (\d+)/)
|
||||
const bookmarkId = idMatch ? idMatch[1] : '1'
|
||||
|
||||
// Update both
|
||||
const result = await client.callTool({
|
||||
name: 'browser_update_bookmark',
|
||||
arguments: {
|
||||
bookmarkId,
|
||||
title: 'New Title',
|
||||
url: 'https://new.example.com',
|
||||
},
|
||||
})
|
||||
|
||||
console.log('\n=== Update Bookmark Both Response ===')
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
|
||||
assert.ok(!result.isError, 'Should succeed')
|
||||
|
||||
const textContent = result.content.find((c) => c.type === 'text')
|
||||
assert.ok(textContent, 'Should have text content')
|
||||
assert.ok(
|
||||
textContent.text.includes('New Title'),
|
||||
'Should include new title',
|
||||
)
|
||||
assert.ok(
|
||||
textContent.text.includes('https://new.example.com'),
|
||||
'Should include new URL',
|
||||
)
|
||||
|
||||
// Cleanup
|
||||
await client.callTool({
|
||||
name: 'browser_remove_bookmark',
|
||||
arguments: { bookmarkId },
|
||||
})
|
||||
})
|
||||
}, 30000)
|
||||
})
|
||||
|
||||
describe('browser_update_bookmark - Error Handling', () => {
|
||||
it('tests that missing bookmarkId is rejected', async () => {
|
||||
await withMcpServer(async (client) => {
|
||||
try {
|
||||
await client.callTool({
|
||||
name: 'browser_update_bookmark',
|
||||
arguments: { title: 'Test' },
|
||||
})
|
||||
assert.fail('Should have thrown validation error')
|
||||
} catch (error) {
|
||||
console.log('\n=== Update Bookmark Missing ID Error ===')
|
||||
console.log(error.message)
|
||||
|
||||
assert.ok(
|
||||
error.message.includes('Invalid arguments') ||
|
||||
error.message.includes('Required'),
|
||||
'Should reject with validation error',
|
||||
)
|
||||
}
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
it('tests that invalid bookmarkId is handled', async () => {
|
||||
await withMcpServer(async (client) => {
|
||||
const result = await client.callTool({
|
||||
name: 'browser_update_bookmark',
|
||||
arguments: {
|
||||
bookmarkId: '999999999',
|
||||
title: 'Test',
|
||||
},
|
||||
})
|
||||
|
||||
console.log('\n=== Update Invalid Bookmark Response ===')
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
|
||||
// Should error with invalid ID
|
||||
assert.ok(result, 'Should return a result')
|
||||
})
|
||||
}, 30000)
|
||||
})
|
||||
|
||||
describe('Bookmark Tools - Response Structure Validation', () => {
|
||||
it('tests that bookmark tools return valid MCP response structure', async () => {
|
||||
await withMcpServer(async (client) => {
|
||||
|
||||
2
bun.lock
2
bun.lock
@@ -1516,7 +1516,7 @@
|
||||
|
||||
"chroma-js": ["chroma-js@3.2.0", "", {}, "sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw=="],
|
||||
|
||||
"chrome-devtools-mcp": ["chrome-devtools-mcp@0.13.0", "", { "bin": { "chrome-devtools-mcp": "build/src/index.js" } }, "sha512-CgotJczVYe6wG2b5cqwNFq7n4VXIM8qfvfutdzVlABPKf0b99b0TDPuRsWrviCthigSHzhpMyFQW03P5Utt1Fg=="],
|
||||
"chrome-devtools-mcp": ["chrome-devtools-mcp@0.14.0", "", { "bin": { "chrome-devtools-mcp": "build/src/index.js" } }, "sha512-JsnA8tApxOZHAUwduMsGFk0Mc3aQF0MX58fo9LoPxJFkyKdq34QonGPGNG38rWXJVQ2X70eI8wosJbOrXN79dQ=="],
|
||||
|
||||
"chrome-launcher": ["chrome-launcher@1.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q=="],
|
||||
|
||||
|
||||
Reference in New Issue
Block a user