refactor(web): align MCP availability context migration

- move MCP availability context to block-selector/context and update imports

- preserve sandbox gating, parent-provider inheritance, and blockedBy semantics

- add context tests on top of refactor baseline cases

- regenerate and prune eslint suppressions
This commit is contained in:
yyh
2026-02-23 22:46:31 +08:00
parent 2dc86363e3
commit 2e0661aa90
11 changed files with 101 additions and 14 deletions

View File

@ -0,0 +1,88 @@
import type { ReactNode } from 'react'
import { renderHook } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { MCPToolAvailabilityProvider, useMCPToolAvailability } from '../mcp-tool-availability-context'
describe('useMCPToolAvailability', () => {
it('returns allowed=true without provider', () => {
const { result } = renderHook(() => useMCPToolAvailability())
expect(result.current).toEqual({ allowed: true })
})
it('returns allowed=true when version is not provided to provider', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
<MCPToolAvailabilityProvider>
{children}
</MCPToolAvailabilityProvider>
)
const { result } = renderHook(() => useMCPToolAvailability(), { wrapper })
expect(result.current).toEqual({ allowed: true })
})
it('returns allowed=false when version is not supported', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
<MCPToolAvailabilityProvider versionSupported={false}>
{children}
</MCPToolAvailabilityProvider>
)
const { result } = renderHook(() => useMCPToolAvailability(), { wrapper })
expect(result.current).toEqual({ allowed: false, blockedBy: 'version' })
})
it('returns allowed=true when version is supported', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
<MCPToolAvailabilityProvider versionSupported={true}>
{children}
</MCPToolAvailabilityProvider>
)
const { result } = renderHook(() => useMCPToolAvailability(), { wrapper })
expect(result.current).toEqual({ allowed: true })
})
it('returns allowed=false when sandbox is not enabled', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
<MCPToolAvailabilityProvider sandboxEnabled={false}>
{children}
</MCPToolAvailabilityProvider>
)
const { result } = renderHook(() => useMCPToolAvailability(), { wrapper })
expect(result.current).toEqual({ allowed: false, blockedBy: 'sandbox' })
})
it('inherits parent provider values when child omits them', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
<MCPToolAvailabilityProvider sandboxEnabled={false}>
<MCPToolAvailabilityProvider versionSupported={true}>
{children}
</MCPToolAvailabilityProvider>
</MCPToolAvailabilityProvider>
)
const { result } = renderHook(() => useMCPToolAvailability(), { wrapper })
expect(result.current).toEqual({ allowed: false, blockedBy: 'sandbox' })
})
it('allows access when child provider overrides parent sandbox value', () => {
const wrapper = ({ children }: { children: ReactNode }) => (
<MCPToolAvailabilityProvider sandboxEnabled={false}>
<MCPToolAvailabilityProvider versionSupported={true} sandboxEnabled={true}>
{children}
</MCPToolAvailabilityProvider>
</MCPToolAvailabilityProvider>
)
const { result } = renderHook(() => useMCPToolAvailability(), { wrapper })
expect(result.current).toEqual({ allowed: true })
})
})

View File

@ -0,0 +1,63 @@
'use client'
import type { ReactNode } from 'react'
import { createContext, useContext, useMemo } from 'react'
type MCPToolAvailabilityContextValue = {
versionSupported?: boolean
sandboxEnabled?: boolean
}
const MCPToolAvailabilityContext = createContext<MCPToolAvailabilityContextValue | undefined>(undefined)
export type MCPToolAvailability = {
allowed: boolean
blockedBy?: 'version' | 'sandbox'
}
type ProviderProps = {
versionSupported?: boolean
sandboxEnabled?: boolean
children: ReactNode
}
export function MCPToolAvailabilityProvider({
versionSupported,
sandboxEnabled,
children,
}: ProviderProps): ReactNode {
const parent = useContext(MCPToolAvailabilityContext)
const value = useMemo<MCPToolAvailabilityContextValue>(() => ({
versionSupported: versionSupported ?? parent?.versionSupported,
sandboxEnabled: sandboxEnabled ?? parent?.sandboxEnabled,
}), [versionSupported, sandboxEnabled, parent])
return (
<MCPToolAvailabilityContext.Provider value={value}>
{children}
</MCPToolAvailabilityContext.Provider>
)
}
// eslint-disable-next-line react-refresh/only-export-components
export function useMCPToolAvailability(): MCPToolAvailability {
const context = useContext(MCPToolAvailabilityContext)
if (!context)
return { allowed: true }
const versionAllowed = context.versionSupported ?? true
const sandboxAllowed = context.sandboxEnabled ?? true
const allowed = versionAllowed && sandboxAllowed
let blockedBy: MCPToolAvailability['blockedBy']
if (!versionAllowed)
blockedBy = 'version'
else if (!sandboxAllowed)
blockedBy = 'sandbox'
if (blockedBy)
return { allowed, blockedBy }
return { allowed }
}