mcp-tool-policy.test.ts5.1 KBView on GitHub import {
buildPolicyFromRows,
canSubmitConnectionForm,
countAllowed,
countNewTools,
describeArgumentRule,
needsPermissionReview,
patchToolRow,
toolRowsFromProbe,
type McpToolRow,
} from '@/modules/integrations/mcp-tool-policy';
const row = (over: Partial<McpToolRow> = {}): McpToolRow => ({
name: 'create_payment_link',
description: 'Create a payment link',
allowed: false,
...over,
});
describe('toolRowsFromProbe', () => {
it('starts every discovered tool denied', () => {
const rows = toolRowsFromProbe([{ name: 'a', description: 'A' }, { name: 'b' }]);
expect(rows).toEqual([
{ name: 'a', description: 'A', allowed: false },
{ name: 'b', description: '', allowed: false },
]);
});
});
describe('patchToolRow', () => {
it('patches by name and never inserts an unknown tool', () => {
const rows = [row({ name: 'a' }), row({ name: 'b' })];
expect(patchToolRow(rows, 'b', { allowed: true })[1].allowed).toBe(true);
expect(patchToolRow(rows, 'zzz', { allowed: true })).toHaveLength(2);
expect(patchToolRow(rows, 'zzz', { allowed: true }).some((r) => r.allowed)).toBe(false);
});
});
describe('buildPolicyFromRows', () => {
it('always writes allowlist mode — saving the checklist is the migration off allow_all', () => {
expect(buildPolicyFromRows([row()]).mode).toBe('allowlist');
});
it('carries the tick, the instruction and the approval flag onto the rule', () => {
const policy = buildPolicyFromRows([
row({ allowed: true, instruction: ' Annual plans only. ', requireApproval: true }),
]);
expect(policy.rules[0]).toMatchObject({
toolName: 'create_payment_link',
allowed: true,
instruction: 'Annual plans only.',
requireApproval: true,
});
});
it('keeps a denied tool in the rules rather than dropping it', () => {
// A dropped rule reads as "never discovered" and comes back badged New — the
// badge has to mean a genuinely new tool, not one the user declined.
const policy = buildPolicyFromRows([row({ name: 'create_refund', allowed: false })]);
expect(policy.rules).toEqual([
{ toolName: 'create_refund', allowed: false, description: 'Create a payment link' },
]);
});
it('preserves argument rules and pinned arguments verbatim', () => {
const policy = buildPolicyFromRows([
row({
allowed: true,
argumentRules: [{ field: 'currency', oneOf: ['usd'] }],
pinnedArguments: { livemode: false },
}),
]);
expect(policy.rules[0].argumentRules).toEqual([{ field: 'currency', oneOf: ['usd'] }]);
expect(policy.rules[0].pinnedArguments).toEqual({ livemode: false });
});
it('carries the discovery stamp through without inventing one', () => {
expect(buildPolicyFromRows([row()], { discoveredAt: '2026-08-25T00:00:00Z' }).discoveredAt).toBe(
'2026-08-25T00:00:00Z',
);
expect(buildPolicyFromRows([row()]).discoveredAt).toBeUndefined();
});
});
describe('needsPermissionReview', () => {
it('flags an allow_all connection — every pre-policy connection was backfilled to it', () => {
expect(needsPermissionReview({ mode: 'allow_all', allowedCount: 0, ruleCount: 0 })).toBe(true);
});
it('does not flag an allowlist connection or a missing digest', () => {
expect(needsPermissionReview({ mode: 'allowlist', allowedCount: 1, ruleCount: 3 })).toBe(false);
expect(needsPermissionReview(null)).toBe(false);
expect(needsPermissionReview(undefined)).toBe(false);
});
});
describe('counts', () => {
it('counts allowed and new tools', () => {
const rows = [row({ name: 'a', allowed: true }), row({ name: 'b', isNew: true })];
expect(countAllowed(rows)).toBe(1);
expect(countNewTools(rows)).toBe(1);
});
});
describe('describeArgumentRule', () => {
it('renders each constraint kind as prose', () => {
expect(describeArgumentRule({ field: 'currency', oneOf: ['usd', 'eur'] })).toBe(
'currency is one of usd, eur',
);
expect(describeArgumentRule({ field: 'op', matches: '^Get' })).toBe('op matches ^Get');
expect(describeArgumentRule({ field: 'id', required: true })).toBe('id is required');
expect(describeArgumentRule({ field: 'id' })).toBe('id');
});
});
describe('canSubmitConnectionForm', () => {
const base = { name: 'Stripe', serverUrl: 'https://mcp.stripe.com', isSaving: false } as const;
it('permits saving after a failed probe — a briefly-down server must not block a connection', () => {
expect(canSubmitConnectionForm({ ...base, probeStatus: 'failed' })).toBe(true);
});
it('permits saving before any probe', () => {
expect(canSubmitConnectionForm({ ...base, probeStatus: 'idle' })).toBe(true);
});
it('blocks only on missing fields, an in-flight probe, or an in-flight save', () => {
expect(canSubmitConnectionForm({ ...base, probeStatus: 'probing' })).toBe(false);
expect(canSubmitConnectionForm({ ...base, probeStatus: 'ready', isSaving: true })).toBe(false);
expect(canSubmitConnectionForm({ ...base, name: ' ', probeStatus: 'ready' })).toBe(false);
expect(canSubmitConnectionForm({ ...base, serverUrl: '', probeStatus: 'ready' })).toBe(false);
});
});