Skip to content

Commit 2270ea2

Browse files
CodebuffAICodebuffAI
authored andcommitted
Fix MCP tool allowlist filtering
1 parent bd97765 commit 2270ea2

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import * as mainPromptModule from '@codebuff/agent-runtime/main-prompt'
2+
import { getInitialSessionState } from '@codebuff/common/types/session-state'
3+
import { getStubProjectFileContext } from '@codebuff/common/util/file'
4+
import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test'
5+
6+
import { CodebuffClient } from '../client'
7+
import * as mcpClientModule from '@codebuff/common/mcp/client'
8+
import * as databaseModule from '../impl/database'
9+
10+
import type { AgentDefinition } from '@codebuff/common/templates/initial-agents-dir/types/agent-definition'
11+
import type { MCPConfig } from '@codebuff/common/types/mcp'
12+
13+
const browserMcpConfig: MCPConfig = {
14+
type: 'stdio',
15+
command: 'npx',
16+
args: ['-y', 'fake-mcp-server'],
17+
env: {},
18+
}
19+
20+
const TEST_AGENT: AgentDefinition = {
21+
id: 'mcp-filter-agent',
22+
displayName: 'MCP Filter Agent',
23+
model: 'openai/gpt-5-mini',
24+
reasoningOptions: { effort: 'minimal' },
25+
mcpServers: {
26+
browser: browserMcpConfig,
27+
},
28+
toolNames: ['browser/browser_navigate', 'browser/browser_snapshot'],
29+
systemPrompt: 'Test MCP filtering.',
30+
}
31+
32+
describe('MCP tool filtering', () => {
33+
afterEach(() => {
34+
mock.restore()
35+
})
36+
37+
it('returns only allowlisted MCP tools when an agent restricts toolNames', async () => {
38+
spyOn(databaseModule, 'getUserInfoFromApiKey').mockResolvedValue({
39+
id: 'user-123',
40+
email: 'test@example.com',
41+
discord_id: null,
42+
referral_code: null,
43+
stripe_customer_id: null,
44+
banned: false,
45+
})
46+
spyOn(databaseModule, 'fetchAgentFromDatabase').mockResolvedValue(null)
47+
spyOn(databaseModule, 'startAgentRun').mockResolvedValue('run-1')
48+
spyOn(databaseModule, 'finishAgentRun').mockResolvedValue(undefined)
49+
spyOn(databaseModule, 'addAgentStep').mockResolvedValue('step-1')
50+
51+
spyOn(mcpClientModule, 'getMCPClient').mockResolvedValue('mcp-client-id')
52+
spyOn(mcpClientModule, 'listMCPTools').mockResolvedValue({
53+
tools: [
54+
{
55+
name: 'browser_navigate',
56+
description: 'Navigate to a page',
57+
inputSchema: { type: 'object', properties: {} },
58+
},
59+
{
60+
name: 'browser_snapshot',
61+
description: 'Capture snapshot',
62+
inputSchema: { type: 'object', properties: {} },
63+
},
64+
{
65+
name: 'browser_click',
66+
description: 'Click an element',
67+
inputSchema: { type: 'object', properties: {} },
68+
},
69+
],
70+
} as Awaited<ReturnType<typeof mcpClientModule.listMCPTools>>)
71+
72+
let filteredTools: Array<{ name: string }> = []
73+
74+
spyOn(mainPromptModule, 'callMainPrompt').mockImplementation(
75+
async (params: Parameters<typeof mainPromptModule.callMainPrompt>[0]) => {
76+
const { sendAction, promptId, requestMcpToolData } = params
77+
const sessionState = getInitialSessionState(getStubProjectFileContext())
78+
79+
filteredTools = await requestMcpToolData({
80+
mcpConfig: browserMcpConfig,
81+
toolNames: TEST_AGENT.toolNames!
82+
.filter((toolName) => toolName.startsWith('browser/'))
83+
.map((toolName) => toolName.slice('browser/'.length)),
84+
})
85+
86+
await sendAction({
87+
action: {
88+
type: 'prompt-response',
89+
promptId,
90+
sessionState,
91+
output: {
92+
type: 'lastMessage',
93+
value: [],
94+
},
95+
},
96+
})
97+
98+
return {
99+
sessionState,
100+
output: {
101+
type: 'lastMessage' as const,
102+
value: [],
103+
},
104+
}
105+
},
106+
)
107+
108+
const client = new CodebuffClient({
109+
apiKey: 'test-key',
110+
agentDefinitions: [TEST_AGENT],
111+
})
112+
113+
const result = await client.run({
114+
agent: TEST_AGENT.id,
115+
prompt: 'List MCP tools',
116+
})
117+
118+
expect(result.output.type).toBe('lastMessage')
119+
expect(filteredTools.map((tool: { name: string }) => tool.name)).toEqual([
120+
'browser_navigate',
121+
'browser_snapshot',
122+
])
123+
})
124+
})

sdk/src/run.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ async function runOnce({
394394
filteredTools.push(tool)
395395
continue
396396
}
397-
if (tool.name in toolNames) {
397+
if (toolNames.includes(tool.name)) {
398398
filteredTools.push(tool)
399399
continue
400400
}

0 commit comments

Comments
 (0)