feat: remove proxyMode — all traffic goes through mcplocal proxy

proxyMode "direct" was a security hole (leaked secrets as plaintext env
vars in .mcp.json) and bypassed all mcplocal features (gating, audit,
RBAC, content pipeline, namespacing). Removed from schema, API, CLI,
and all tests. Old configs with proxyMode are accepted but silently
stripped via Zod .transform() for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michal
2026-03-07 23:36:36 +00:00
parent d9d0a7a374
commit 0995851810
28 changed files with 69 additions and 221 deletions

View File

@@ -332,7 +332,6 @@ rbacBindings:
projects:
- name: smart-home
description: Home automation
proxyMode: filtered
llmProvider: gemini-cli
llmModel: gemini-2.0-flash
servers:
@@ -345,7 +344,6 @@ projects:
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
name: 'smart-home',
proxyMode: 'filtered',
llmProvider: 'gemini-cli',
llmModel: 'gemini-2.0-flash',
servers: ['my-grafana', 'my-ha'],

View File

@@ -175,7 +175,6 @@ describe('create command', () => {
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', {
name: 'my-project',
description: 'A test project',
proxyMode: 'direct',
});
expect(output.join('\n')).toContain("project 'test' created");
});
@@ -186,7 +185,6 @@ describe('create command', () => {
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', {
name: 'minimal',
description: '',
proxyMode: 'direct',
});
});
@@ -195,7 +193,7 @@ describe('create command', () => {
vi.mocked(client.get).mockResolvedValueOnce([{ id: 'proj-1', name: 'my-proj' }] as never);
const cmd = createCreateCommand({ client, log });
await cmd.parseAsync(['project', 'my-proj', '-d', 'updated', '--force'], { from: 'user' });
expect(client.put).toHaveBeenCalledWith('/api/v1/projects/proj-1', { description: 'updated', proxyMode: 'direct' });
expect(client.put).toHaveBeenCalledWith('/api/v1/projects/proj-1', { description: 'updated' });
expect(output.join('\n')).toContain("project 'my-proj' updated");
});
});

View File

@@ -96,7 +96,6 @@ describe('describe command', () => {
description: 'A gated project',
ownerId: 'user-1',
proxyModel: 'default',
proxyMode: 'direct',
createdAt: '2025-01-01',
});
const cmd = createDescribeCommand(deps);

View File

@@ -179,7 +179,6 @@ describe('get command', () => {
id: 'proj-1',
name: 'smart-home',
description: 'Home automation',
proxyMode: 'filtered',
ownerId: 'usr-1',
servers: [{ server: { name: 'grafana' } }],
}]);
@@ -187,10 +186,8 @@ describe('get command', () => {
await cmd.parseAsync(['node', 'test', 'projects']);
const text = deps.output.join('\n');
expect(text).toContain('MODE');
expect(text).toContain('SERVERS');
expect(text).toContain('smart-home');
expect(text).toContain('filtered');
expect(text).toContain('1');
});
@@ -341,7 +338,6 @@ describe('get command', () => {
id: 'proj-1',
name: 'home',
description: '',
proxyMode: 'direct',
proxyModel: '',
gated: true,
ownerId: 'usr-1',
@@ -362,7 +358,6 @@ describe('get command', () => {
id: 'proj-1',
name: 'home',
description: '',
proxyMode: 'direct',
proxyModel: '',
gated: true,
ownerId: 'usr-1',
@@ -381,7 +376,6 @@ describe('get command', () => {
id: 'proj-1',
name: 'tools',
description: '',
proxyMode: 'direct',
proxyModel: '',
gated: false,
ownerId: 'usr-1',
@@ -400,7 +394,6 @@ describe('get command', () => {
id: 'proj-1',
name: 'custom',
description: '',
proxyMode: 'direct',
proxyModel: 'gate',
gated: true,
ownerId: 'usr-1',

View File

@@ -24,12 +24,11 @@ describe('project with new fields', () => {
});
describe('create project with enhanced options', () => {
it('creates project with proxy mode and servers', async () => {
it('creates project with servers', async () => {
const cmd = createCreateCommand({ client, log });
await cmd.parseAsync([
'project', 'smart-home',
'-d', 'Smart home project',
'--proxy-mode', 'filtered',
'--server', 'my-grafana',
'--server', 'my-ha',
], { from: 'user' });
@@ -37,30 +36,19 @@ describe('project with new fields', () => {
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
name: 'smart-home',
description: 'Smart home project',
proxyMode: 'filtered',
servers: ['my-grafana', 'my-ha'],
}));
});
it('defaults proxy mode to direct', async () => {
const cmd = createCreateCommand({ client, log });
await cmd.parseAsync(['project', 'basic'], { from: 'user' });
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
proxyMode: 'direct',
}));
});
});
describe('get projects shows new columns', () => {
it('shows MODE and SERVERS columns', async () => {
it('shows SERVERS column', async () => {
const deps = {
output: [] as string[],
fetchResource: vi.fn(async () => [{
id: 'proj-1',
name: 'smart-home',
description: 'Test',
proxyMode: 'filtered',
ownerId: 'user-1',
servers: [{ server: { name: 'grafana' } }, { server: { name: 'ha' } }],
}]),
@@ -70,14 +58,13 @@ describe('project with new fields', () => {
await cmd.parseAsync(['node', 'test', 'projects']);
const text = deps.output.join('\n');
expect(text).toContain('MODE');
expect(text).toContain('SERVERS');
expect(text).toContain('smart-home');
});
});
describe('describe project shows full detail', () => {
it('shows servers and proxy config', async () => {
it('shows servers and LLM config', async () => {
const deps = {
output: [] as string[],
client: mockClient(),
@@ -85,7 +72,6 @@ describe('project with new fields', () => {
id: 'proj-1',
name: 'smart-home',
description: 'Smart home',
proxyMode: 'filtered',
llmProvider: 'gemini-cli',
llmModel: 'gemini-2.0-flash',
ownerId: 'user-1',
@@ -103,7 +89,6 @@ describe('project with new fields', () => {
const text = deps.output.join('\n');
expect(text).toContain('=== Project: smart-home ===');
expect(text).toContain('filtered');
expect(text).toContain('gemini-cli');
expect(text).toContain('my-grafana');
expect(text).toContain('my-ha');