diff --git a/packages/internal/src/db/migrations/meta/_journal.json b/packages/internal/src/db/migrations/meta/_journal.json index 8952549c98..a8183fcf3e 100644 --- a/packages/internal/src/db/migrations/meta/_journal.json +++ b/packages/internal/src/db/migrations/meta/_journal.json @@ -279,7 +279,7 @@ "idx": 39, "version": "7", "when": 1770252529987, - "tag": "0039_bumpy_vertigo", + "tag": "0039_quiet_franklin_storm", "breakpoints": true }, { diff --git a/sdk/src/__tests__/run-mcp-tool-filter.test.ts b/sdk/src/__tests__/run-mcp-tool-filter.test.ts new file mode 100644 index 0000000000..0b0b0a8b7e --- /dev/null +++ b/sdk/src/__tests__/run-mcp-tool-filter.test.ts @@ -0,0 +1,124 @@ +import * as mainPromptModule from '@codebuff/agent-runtime/main-prompt' +import { getInitialSessionState } from '@codebuff/common/types/session-state' +import { getStubProjectFileContext } from '@codebuff/common/util/file' +import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test' + +import { CodebuffClient } from '../client' +import * as mcpClientModule from '@codebuff/common/mcp/client' +import * as databaseModule from '../impl/database' + +import type { AgentDefinition } from '@codebuff/common/templates/initial-agents-dir/types/agent-definition' +import type { MCPConfig } from '@codebuff/common/types/mcp' + +const browserMcpConfig: MCPConfig = { + type: 'stdio', + command: 'npx', + args: ['-y', 'fake-mcp-server'], + env: {}, +} + +const TEST_AGENT: AgentDefinition = { + id: 'mcp-filter-agent', + displayName: 'MCP Filter Agent', + model: 'openai/gpt-5-mini', + reasoningOptions: { effort: 'minimal' }, + mcpServers: { + browser: browserMcpConfig, + }, + toolNames: ['browser/browser_navigate', 'browser/browser_snapshot'], + systemPrompt: 'Test MCP filtering.', +} + +describe('MCP tool filtering', () => { + afterEach(() => { + mock.restore() + }) + + it('returns only allowlisted MCP tools when an agent restricts toolNames', async () => { + spyOn(databaseModule, 'getUserInfoFromApiKey').mockResolvedValue({ + id: 'user-123', + email: 'test@example.com', + discord_id: null, + referral_code: null, + stripe_customer_id: null, + banned: false, + }) + spyOn(databaseModule, 'fetchAgentFromDatabase').mockResolvedValue(null) + spyOn(databaseModule, 'startAgentRun').mockResolvedValue('run-1') + spyOn(databaseModule, 'finishAgentRun').mockResolvedValue(undefined) + spyOn(databaseModule, 'addAgentStep').mockResolvedValue('step-1') + + spyOn(mcpClientModule, 'getMCPClient').mockResolvedValue('mcp-client-id') + spyOn(mcpClientModule, 'listMCPTools').mockResolvedValue({ + tools: [ + { + name: 'browser_navigate', + description: 'Navigate to a page', + inputSchema: { type: 'object', properties: {} }, + }, + { + name: 'browser_snapshot', + description: 'Capture snapshot', + inputSchema: { type: 'object', properties: {} }, + }, + { + name: 'browser_click', + description: 'Click an element', + inputSchema: { type: 'object', properties: {} }, + }, + ], + } as Awaited>) + + let filteredTools: Array<{ name: string }> = [] + + spyOn(mainPromptModule, 'callMainPrompt').mockImplementation( + async (params: Parameters[0]) => { + const { sendAction, promptId, requestMcpToolData } = params + const sessionState = getInitialSessionState(getStubProjectFileContext()) + + filteredTools = await requestMcpToolData({ + mcpConfig: browserMcpConfig, + toolNames: TEST_AGENT.toolNames! + .filter((toolName) => toolName.startsWith('browser/')) + .map((toolName) => toolName.slice('browser/'.length)), + }) + + await sendAction({ + action: { + type: 'prompt-response', + promptId, + sessionState, + output: { + type: 'lastMessage', + value: [], + }, + }, + }) + + return { + sessionState, + output: { + type: 'lastMessage' as const, + value: [], + }, + } + }, + ) + + const client = new CodebuffClient({ + apiKey: 'test-key', + agentDefinitions: [TEST_AGENT], + }) + + const result = await client.run({ + agent: TEST_AGENT.id, + prompt: 'List MCP tools', + }) + + expect(result.output.type).toBe('lastMessage') + expect(filteredTools.map((tool: { name: string }) => tool.name)).toEqual([ + 'browser_navigate', + 'browser_snapshot', + ]) + }) +}) diff --git a/sdk/src/run.ts b/sdk/src/run.ts index f0d150ca01..57b42ffbd3 100644 --- a/sdk/src/run.ts +++ b/sdk/src/run.ts @@ -394,7 +394,7 @@ async function runOnce({ filteredTools.push(tool) continue } - if (tool.name in toolNames) { + if (toolNames.includes(tool.name)) { filteredTools.push(tool) continue } diff --git a/web/src/llm-api/fireworks.ts b/web/src/llm-api/fireworks.ts index aa915f1529..e93747224f 100644 --- a/web/src/llm-api/fireworks.ts +++ b/web/src/llm-api/fireworks.ts @@ -43,17 +43,9 @@ const FIREWORKS_DEPLOYMENT_MAP: Record = { 'z-ai/glm-5.1': 'accounts/james-65d217/deployments/mjb4i7ea', } -/** Check if current time is within deployment hours (10am–8pm ET) */ -export function isDeploymentHours(now: Date = new Date()): boolean { - const etHour = parseInt( - now.toLocaleString('en-US', { - timeZone: 'America/New_York', - hour: 'numeric', - hour12: false, - }), - 10, - ) - return etHour >= 10 && etHour < 20 +/** Check if current time is within deployment hours (always enabled) */ +export function isDeploymentHours(_now: Date = new Date()): boolean { + return true } /** @@ -731,7 +723,7 @@ export async function createFireworksRequestWithFallback(params: { if (shouldTryDeployment) { logger.info( { model: originalModel, deploymentModel: deploymentModelId }, - 'Trying Fireworks custom deployment (business hours)', + 'Trying Fireworks custom deployment', ) const response = await createFireworksRequest({ body,