* fix: OAuth tokens secure storage for Windows & Linux * fix(mcp): MCP Tool Re-exposure & Strict Input Validation Fixes the MCP re-exposure bug by correctly handling tool deduplication, input validation with Ajv, and structured output (including images). Also disables experimental API betas by default to prevent 500 errors on external accounts. * fix(mcp): skip official registry prefetch in non-first-party mode Prevents unnecessary calls to Anthropic's MCP registry when using other API providers. * fix(cli): disable experimental API betas by default This prevents 500 errors from Anthropic's API when tool-calling with non-Anthropic accounts or models that don't support certain beta features. * fix: issues raised in the PR review for #675
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import { describe, it, expect, mock } from 'bun:test'
|
|
import { getCombinedTools, loadReexposedMcpTools } from './mcp.js'
|
|
import type { Tool as InternalTool } from '../Tool.js'
|
|
import type { MCPServerConnection } from '../services/mcp/types.js'
|
|
import type { Tool } from '@modelcontextprotocol/sdk/types.js'
|
|
|
|
// Mock the MCP client service to control the tools and connections returned
|
|
const mockGetMcpToolsCommandsAndResources = mock(async (onConnectionAttempt: any) => {})
|
|
mock.module('../services/mcp/client.js', () => ({
|
|
getMcpToolsCommandsAndResources: mockGetMcpToolsCommandsAndResources
|
|
}))
|
|
|
|
describe('getCombinedTools', () => {
|
|
it('deduplicates builtins when mcpTools have the same name, prioritizing mcpTools', () => {
|
|
const builtinBash = { name: 'Bash', isMcp: false } as unknown as InternalTool
|
|
const builtinRead = { name: 'Read', isMcp: false } as unknown as InternalTool
|
|
const mcpBash = { name: 'Bash', isMcp: true } as unknown as InternalTool
|
|
|
|
const builtins = [builtinBash, builtinRead]
|
|
const mcpTools = [mcpBash]
|
|
|
|
const result = getCombinedTools(builtins, mcpTools)
|
|
|
|
expect(result).toHaveLength(2)
|
|
expect(result[0]).toBe(mcpBash)
|
|
expect(result[1]).toBe(builtinRead)
|
|
})
|
|
})
|
|
|
|
describe('loadReexposedMcpTools', () => {
|
|
it('loads tools and clients regardless of connection state (including needs-auth)', async () => {
|
|
// Setup the mock to simulate yielding a needs-auth server and a connected server
|
|
mockGetMcpToolsCommandsAndResources.mockImplementation(async (onConnectionAttempt) => {
|
|
const needsAuthClient = {
|
|
name: 'auth-server',
|
|
type: 'needs-auth',
|
|
config: {}
|
|
} as MCPServerConnection
|
|
|
|
const authTool = {
|
|
name: 'mcp__auth-server__authenticate',
|
|
isMcp: true
|
|
} as unknown as InternalTool
|
|
|
|
const connectedClient = {
|
|
name: 'connected-server',
|
|
type: 'connected',
|
|
config: {},
|
|
client: {}
|
|
} as MCPServerConnection
|
|
|
|
const connectedTool = {
|
|
name: 'mcp__connected-server__do_thing',
|
|
isMcp: true
|
|
} as unknown as InternalTool
|
|
|
|
// Simulate the callback behavior
|
|
onConnectionAttempt({ client: needsAuthClient, tools: [authTool], commands: [] })
|
|
onConnectionAttempt({ client: connectedClient, tools: [connectedTool], commands: [] })
|
|
})
|
|
|
|
const { mcpClients, mcpTools } = await loadReexposedMcpTools()
|
|
|
|
expect(mcpClients).toHaveLength(2)
|
|
expect(mcpClients[0].type).toBe('needs-auth')
|
|
expect(mcpClients[1].type).toBe('connected')
|
|
|
|
expect(mcpTools).toHaveLength(2)
|
|
expect(mcpTools[0].name).toBe('mcp__auth-server__authenticate')
|
|
expect(mcpTools[1].name).toBe('mcp__connected-server__do_thing')
|
|
|
|
// Reset mock for other tests
|
|
mockGetMcpToolsCommandsAndResources.mockReset()
|
|
})
|
|
})
|