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:
@@ -144,7 +144,6 @@ export async function bootstrapSystemProject(prisma: PrismaClient): Promise<void
|
||||
name: SYSTEM_PROJECT_NAME,
|
||||
description: 'System prompts for mcpctl gating and session management',
|
||||
prompt: '',
|
||||
proxyMode: 'direct',
|
||||
gated: false,
|
||||
ownerId: systemUser.id,
|
||||
},
|
||||
|
||||
@@ -276,7 +276,7 @@ async function main(): Promise<void> {
|
||||
const instanceService = new InstanceService(instanceRepo, serverRepo, orchestrator, secretRepo);
|
||||
serverService.setInstanceService(instanceService);
|
||||
const secretService = new SecretService(secretRepo);
|
||||
const projectService = new ProjectService(projectRepo, serverRepo, secretRepo);
|
||||
const projectService = new ProjectService(projectRepo, serverRepo);
|
||||
const auditLogService = new AuditLogService(auditLogRepo);
|
||||
const auditEventService = new AuditEventService(auditEventRepo);
|
||||
const metricsCollector = new MetricsCollector();
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface IProjectRepository {
|
||||
findAll(ownerId?: string): Promise<ProjectWithRelations[]>;
|
||||
findById(id: string): Promise<ProjectWithRelations | null>;
|
||||
findByName(name: string): Promise<ProjectWithRelations | null>;
|
||||
create(data: { name: string; description: string; prompt?: string; ownerId: string; proxyMode: string; proxyModel?: string; gated?: boolean; llmProvider?: string; llmModel?: string; serverOverrides?: Record<string, unknown> }): Promise<ProjectWithRelations>;
|
||||
create(data: { name: string; description: string; prompt?: string; ownerId: string; proxyModel?: string; gated?: boolean; llmProvider?: string; llmModel?: string; serverOverrides?: Record<string, unknown> }): Promise<ProjectWithRelations>;
|
||||
update(id: string, data: Record<string, unknown>): Promise<ProjectWithRelations>;
|
||||
delete(id: string): Promise<void>;
|
||||
setServers(projectId: string, serverIds: string[]): Promise<void>;
|
||||
@@ -36,12 +36,11 @@ export class ProjectRepository implements IProjectRepository {
|
||||
return this.prisma.project.findUnique({ where: { name }, include: PROJECT_INCLUDE }) as unknown as Promise<ProjectWithRelations | null>;
|
||||
}
|
||||
|
||||
async create(data: { name: string; description: string; prompt?: string; ownerId: string; proxyMode: string; proxyModel?: string; gated?: boolean; llmProvider?: string; llmModel?: string; serverOverrides?: Record<string, unknown> }): Promise<ProjectWithRelations> {
|
||||
async create(data: { name: string; description: string; prompt?: string; ownerId: string; proxyModel?: string; gated?: boolean; llmProvider?: string; llmModel?: string; serverOverrides?: Record<string, unknown> }): Promise<ProjectWithRelations> {
|
||||
const createData: Record<string, unknown> = {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
ownerId: data.ownerId,
|
||||
proxyMode: data.proxyMode,
|
||||
};
|
||||
if (data.prompt !== undefined) createData['prompt'] = data.prompt;
|
||||
if (data.proxyModel !== undefined) createData['proxyModel'] = data.proxyModel;
|
||||
|
||||
@@ -116,7 +116,6 @@ export class BackupService {
|
||||
projects = allProjects.map((proj) => ({
|
||||
name: proj.name,
|
||||
description: proj.description,
|
||||
proxyMode: proj.proxyMode,
|
||||
proxyModel: proj.proxyModel,
|
||||
llmProvider: proj.llmProvider,
|
||||
llmModel: proj.llmModel,
|
||||
|
||||
@@ -255,7 +255,6 @@ export class RestoreService {
|
||||
}
|
||||
// overwrite
|
||||
const updateData: Record<string, unknown> = { description: project.description };
|
||||
if (project.proxyMode) updateData['proxyMode'] = project.proxyMode;
|
||||
if (project.proxyModel) updateData['proxyModel'] = project.proxyModel;
|
||||
if (project.llmProvider !== undefined) updateData['llmProvider'] = project.llmProvider;
|
||||
if (project.llmModel !== undefined) updateData['llmModel'] = project.llmModel;
|
||||
@@ -271,11 +270,10 @@ export class RestoreService {
|
||||
continue;
|
||||
}
|
||||
|
||||
const projectCreateData: { name: string; description: string; ownerId: string; proxyMode: string; proxyModel?: string; llmProvider?: string; llmModel?: string } = {
|
||||
const projectCreateData: { name: string; description: string; ownerId: string; proxyModel?: string; llmProvider?: string; llmModel?: string } = {
|
||||
name: project.name,
|
||||
description: project.description,
|
||||
ownerId: 'system',
|
||||
proxyMode: project.proxyMode ?? 'direct',
|
||||
};
|
||||
if (project.proxyModel) projectCreateData.proxyModel = project.proxyModel;
|
||||
if (project.llmProvider != null) projectCreateData.llmProvider = project.llmProvider;
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import type { McpServer } from '@prisma/client';
|
||||
import type { IProjectRepository, ProjectWithRelations } from '../repositories/project.repository.js';
|
||||
import type { IMcpServerRepository, ISecretRepository } from '../repositories/interfaces.js';
|
||||
import type { IMcpServerRepository } from '../repositories/interfaces.js';
|
||||
import { CreateProjectSchema, UpdateProjectSchema } from '../validation/project.schema.js';
|
||||
import { NotFoundError, ConflictError } from './mcp-server.service.js';
|
||||
import { resolveServerEnv } from './env-resolver.js';
|
||||
import { generateMcpConfig } from './mcp-config-generator.js';
|
||||
import type { McpConfig } from './mcp-config-generator.js';
|
||||
|
||||
export class ProjectService {
|
||||
constructor(
|
||||
private readonly projectRepo: IProjectRepository,
|
||||
private readonly serverRepo: IMcpServerRepository,
|
||||
private readonly secretRepo: ISecretRepository,
|
||||
) {}
|
||||
|
||||
async list(ownerId?: string): Promise<ProjectWithRelations[]> {
|
||||
@@ -55,7 +51,6 @@ export class ProjectService {
|
||||
description: data.description,
|
||||
prompt: data.prompt,
|
||||
ownerId,
|
||||
proxyMode: data.proxyMode,
|
||||
proxyModel: data.proxyModel,
|
||||
gated: data.gated,
|
||||
...(data.llmProvider !== undefined ? { llmProvider: data.llmProvider } : {}),
|
||||
@@ -80,7 +75,6 @@ export class ProjectService {
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (data.description !== undefined) updateData['description'] = data.description;
|
||||
if (data.prompt !== undefined) updateData['prompt'] = data.prompt;
|
||||
if (data.proxyMode !== undefined) updateData['proxyMode'] = data.proxyMode;
|
||||
if (data.proxyModel !== undefined) updateData['proxyModel'] = data.proxyModel;
|
||||
if (data.llmProvider !== undefined) updateData['llmProvider'] = data.llmProvider;
|
||||
if (data.llmModel !== undefined) updateData['llmModel'] = data.llmModel;
|
||||
@@ -110,29 +104,14 @@ export class ProjectService {
|
||||
async generateMcpConfig(idOrName: string): Promise<McpConfig> {
|
||||
const project = await this.resolveAndGet(idOrName);
|
||||
|
||||
if (project.proxyMode === 'filtered') {
|
||||
// Single entry pointing at mcplocal proxy
|
||||
return {
|
||||
mcpServers: {
|
||||
[project.name]: {
|
||||
url: `http://localhost:3100/api/v1/mcp/proxy/project/${project.name}`,
|
||||
},
|
||||
// All traffic goes through mcplocal proxy — single entry
|
||||
return {
|
||||
mcpServers: {
|
||||
[project.name]: {
|
||||
url: `http://localhost:3100/api/v1/mcp/proxy/project/${project.name}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Direct mode: fetch full servers and resolve env
|
||||
const serverEntries: Array<{ server: McpServer; resolvedEnv: Record<string, string> }> = [];
|
||||
|
||||
for (const ps of project.servers) {
|
||||
const server = await this.serverRepo.findById(ps.server.id);
|
||||
if (server === null) continue;
|
||||
|
||||
const resolvedEnv = await resolveServerEnv(server, this.secretRepo);
|
||||
serverEntries.push({ server, resolvedEnv });
|
||||
}
|
||||
|
||||
return generateMcpConfig(serverEntries);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async addServer(idOrName: string, serverName: string): Promise<ProjectWithRelations> {
|
||||
|
||||
@@ -4,7 +4,6 @@ export const CreateProjectSchema = z.object({
|
||||
name: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/, 'Name must be lowercase alphanumeric with hyphens'),
|
||||
description: z.string().max(1000).default(''),
|
||||
prompt: z.string().max(10000).default(''),
|
||||
proxyMode: z.enum(['direct', 'filtered']).default('direct'),
|
||||
proxyModel: z.string().max(100).default(''),
|
||||
gated: z.boolean().default(true),
|
||||
llmProvider: z.string().max(100).optional(),
|
||||
@@ -13,15 +12,13 @@ export const CreateProjectSchema = z.object({
|
||||
serverOverrides: z.record(z.string(), z.object({
|
||||
proxyModel: z.string().optional(),
|
||||
})).optional(),
|
||||
}).refine(
|
||||
(d) => d.proxyMode !== 'filtered' || d.llmProvider,
|
||||
{ message: 'llmProvider is required when proxyMode is "filtered"' },
|
||||
);
|
||||
// Backward compat: accept but ignore proxyMode from old configs
|
||||
proxyMode: z.string().optional(),
|
||||
}).transform(({ proxyMode: _ignored, ...rest }) => rest);
|
||||
|
||||
export const UpdateProjectSchema = z.object({
|
||||
description: z.string().max(1000).optional(),
|
||||
prompt: z.string().max(10000).optional(),
|
||||
proxyMode: z.enum(['direct', 'filtered']).optional(),
|
||||
proxyModel: z.string().max(100).optional(),
|
||||
gated: z.boolean().optional(),
|
||||
llmProvider: z.string().max(100).nullable().optional(),
|
||||
@@ -30,7 +27,9 @@ export const UpdateProjectSchema = z.object({
|
||||
serverOverrides: z.record(z.string(), z.object({
|
||||
proxyModel: z.string().optional(),
|
||||
})).optional(),
|
||||
});
|
||||
// Backward compat: accept but ignore proxyMode from old configs
|
||||
proxyMode: z.string().optional(),
|
||||
}).transform(({ proxyMode: _ignored, ...rest }) => rest);
|
||||
|
||||
export type CreateProjectInput = z.infer<typeof CreateProjectSchema>;
|
||||
export type UpdateProjectInput = z.infer<typeof UpdateProjectSchema>;
|
||||
|
||||
@@ -34,7 +34,7 @@ const mockSecrets = [
|
||||
|
||||
const mockProjects = [
|
||||
{
|
||||
id: 'proj1', name: 'my-project', description: 'Test project', proxyMode: 'direct', proxyModel: '', llmProvider: null, llmModel: null,
|
||||
id: 'proj1', name: 'my-project', description: 'Test project', proxyModel: '', llmProvider: null, llmModel: null,
|
||||
ownerId: 'user1', version: 1, createdAt: new Date(), updatedAt: new Date(),
|
||||
servers: [{ id: 'ps1', server: { id: 's1', name: 'github' } }],
|
||||
},
|
||||
@@ -217,7 +217,6 @@ describe('BackupService', () => {
|
||||
it('includes enriched projects with server names', async () => {
|
||||
const bundle = await backupService.createBackup();
|
||||
const proj = bundle.projects[0]!;
|
||||
expect(proj.proxyMode).toBe('direct');
|
||||
expect(proj.serverNames).toEqual(['github']);
|
||||
});
|
||||
|
||||
@@ -322,7 +321,7 @@ describe('RestoreService', () => {
|
||||
{ name: 'admins', subjects: [{ kind: 'User', name: 'alice@test.com' }], roleBindings: [{ role: 'edit', resource: '*' }] },
|
||||
],
|
||||
projects: [
|
||||
{ name: 'test-proj', description: 'Test', proxyMode: 'filtered', llmProvider: 'openai', llmModel: 'gpt-4', serverNames: ['github'], members: ['alice@test.com'] },
|
||||
{ name: 'test-proj', description: 'Test', llmProvider: 'openai', llmModel: 'gpt-4', serverNames: ['github'], members: ['alice@test.com'] },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -423,7 +422,6 @@ describe('RestoreService', () => {
|
||||
expect(result.projectsCreated).toBe(1);
|
||||
expect(projectRepo.create).toHaveBeenCalledWith(expect.objectContaining({
|
||||
name: 'test-proj',
|
||||
proxyMode: 'filtered',
|
||||
llmProvider: 'openai',
|
||||
llmModel: 'gpt-4',
|
||||
}));
|
||||
|
||||
@@ -5,7 +5,7 @@ import { registerProjectRoutes } from '../src/routes/projects.js';
|
||||
import { ProjectService } from '../src/services/project.service.js';
|
||||
import { errorHandler } from '../src/middleware/error-handler.js';
|
||||
import type { IProjectRepository, ProjectWithRelations } from '../src/repositories/project.repository.js';
|
||||
import type { IMcpServerRepository, ISecretRepository } from '../src/repositories/interfaces.js';
|
||||
import type { IMcpServerRepository } from '../src/repositories/interfaces.js';
|
||||
|
||||
let app: FastifyInstance;
|
||||
|
||||
@@ -15,7 +15,6 @@ function makeProject(overrides: Partial<ProjectWithRelations> = {}): ProjectWith
|
||||
name: 'test-project',
|
||||
description: '',
|
||||
ownerId: 'user-1',
|
||||
proxyMode: 'direct',
|
||||
prompt: '',
|
||||
proxyModel: '',
|
||||
gated: true,
|
||||
@@ -39,7 +38,6 @@ function mockProjectRepo(): IProjectRepository {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
ownerId: data.ownerId,
|
||||
proxyMode: data.proxyMode,
|
||||
})),
|
||||
update: vi.fn(async (_id, data) => makeProject({ ...data as Partial<ProjectWithRelations> })),
|
||||
delete: vi.fn(async () => {}),
|
||||
@@ -60,17 +58,6 @@ function mockServerRepo(): IMcpServerRepository {
|
||||
};
|
||||
}
|
||||
|
||||
function mockSecretRepo(): ISecretRepository {
|
||||
return {
|
||||
findAll: vi.fn(async () => []),
|
||||
findById: vi.fn(async () => null),
|
||||
findByName: vi.fn(async () => null),
|
||||
create: vi.fn(async () => ({} as never)),
|
||||
update: vi.fn(async () => ({} as never)),
|
||||
delete: vi.fn(async () => {}),
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
if (app) await app.close();
|
||||
});
|
||||
@@ -78,7 +65,7 @@ afterEach(async () => {
|
||||
function createApp(projectRepo: IProjectRepository, serverRepo?: IMcpServerRepository) {
|
||||
app = Fastify({ logger: false });
|
||||
app.setErrorHandler(errorHandler);
|
||||
const service = new ProjectService(projectRepo, serverRepo ?? mockServerRepo(), mockSecretRepo());
|
||||
const service = new ProjectService(projectRepo, serverRepo ?? mockServerRepo());
|
||||
registerProjectRoutes(app, service);
|
||||
return app.ready();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ProjectService } from '../src/services/project.service.js';
|
||||
import { NotFoundError, ConflictError } from '../src/services/mcp-server.service.js';
|
||||
import type { IProjectRepository, ProjectWithRelations } from '../src/repositories/project.repository.js';
|
||||
import type { IMcpServerRepository, ISecretRepository } from '../src/repositories/interfaces.js';
|
||||
import type { IMcpServerRepository } from '../src/repositories/interfaces.js';
|
||||
import type { McpServer } from '@prisma/client';
|
||||
|
||||
function makeProject(overrides: Partial<ProjectWithRelations> = {}): ProjectWithRelations {
|
||||
@@ -11,7 +11,6 @@ function makeProject(overrides: Partial<ProjectWithRelations> = {}): ProjectWith
|
||||
name: 'test-project',
|
||||
description: '',
|
||||
ownerId: 'user-1',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: '',
|
||||
gated: true,
|
||||
llmProvider: null,
|
||||
@@ -57,7 +56,6 @@ function mockProjectRepo(): IProjectRepository {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
ownerId: data.ownerId,
|
||||
proxyMode: data.proxyMode,
|
||||
llmProvider: data.llmProvider ?? null,
|
||||
llmModel: data.llmModel ?? null,
|
||||
})),
|
||||
@@ -80,28 +78,15 @@ function mockServerRepo(): IMcpServerRepository {
|
||||
};
|
||||
}
|
||||
|
||||
function mockSecretRepo(): ISecretRepository {
|
||||
return {
|
||||
findAll: vi.fn(async () => []),
|
||||
findById: vi.fn(async () => null),
|
||||
findByName: vi.fn(async () => null),
|
||||
create: vi.fn(async () => ({ id: 'sec-1', name: 'test', data: {}, version: 1, createdAt: new Date(), updatedAt: new Date() })),
|
||||
update: vi.fn(async () => ({ id: 'sec-1', name: 'test', data: {}, version: 1, createdAt: new Date(), updatedAt: new Date() })),
|
||||
delete: vi.fn(async () => {}),
|
||||
};
|
||||
}
|
||||
|
||||
describe('ProjectService', () => {
|
||||
let projectRepo: ReturnType<typeof mockProjectRepo>;
|
||||
let serverRepo: ReturnType<typeof mockServerRepo>;
|
||||
let secretRepo: ReturnType<typeof mockSecretRepo>;
|
||||
let service: ProjectService;
|
||||
|
||||
beforeEach(() => {
|
||||
projectRepo = mockProjectRepo();
|
||||
serverRepo = mockServerRepo();
|
||||
secretRepo = mockSecretRepo();
|
||||
service = new ProjectService(projectRepo, serverRepo, secretRepo);
|
||||
service = new ProjectService(projectRepo, serverRepo);
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
@@ -149,27 +134,6 @@ describe('ProjectService', () => {
|
||||
expect(result.servers).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('creates project with proxyMode and llmProvider', async () => {
|
||||
const created = makeProject({ id: 'proj-filtered', proxyMode: 'filtered', llmProvider: 'openai' });
|
||||
vi.mocked(projectRepo.create).mockResolvedValue(created);
|
||||
vi.mocked(projectRepo.findById).mockResolvedValue(created);
|
||||
|
||||
const result = await service.create({
|
||||
name: 'filtered-proj',
|
||||
proxyMode: 'filtered',
|
||||
llmProvider: 'openai',
|
||||
}, 'user-1');
|
||||
|
||||
expect(result.proxyMode).toBe('filtered');
|
||||
expect(result.llmProvider).toBe('openai');
|
||||
});
|
||||
|
||||
it('rejects filtered project without llmProvider', async () => {
|
||||
await expect(
|
||||
service.create({ name: 'bad-proj', proxyMode: 'filtered' }, 'user-1'),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('throws NotFoundError when server name resolution fails', async () => {
|
||||
vi.mocked(serverRepo.findByName).mockResolvedValue(null);
|
||||
|
||||
@@ -226,13 +190,12 @@ describe('ProjectService', () => {
|
||||
expect(projectRepo.setServers).toHaveBeenCalledWith('proj-1', ['srv-new']);
|
||||
});
|
||||
|
||||
it('updates proxyMode', async () => {
|
||||
it('updates llmProvider', async () => {
|
||||
const existing = makeProject({ id: 'proj-1' });
|
||||
vi.mocked(projectRepo.findById).mockResolvedValue(existing);
|
||||
|
||||
await service.update('proj-1', { proxyMode: 'filtered', llmProvider: 'anthropic' });
|
||||
await service.update('proj-1', { llmProvider: 'anthropic' });
|
||||
expect(projectRepo.update).toHaveBeenCalledWith('proj-1', {
|
||||
proxyMode: 'filtered',
|
||||
llmProvider: 'anthropic',
|
||||
});
|
||||
});
|
||||
@@ -297,46 +260,10 @@ describe('ProjectService', () => {
|
||||
});
|
||||
|
||||
describe('generateMcpConfig', () => {
|
||||
it('generates direct mode config with STDIO servers', async () => {
|
||||
const srv = makeServer({ id: 'srv-1', name: 'github', packageName: '@mcp/github', transport: 'STDIO' });
|
||||
it('generates single mcplocal proxy entry', async () => {
|
||||
const project = makeProject({
|
||||
id: 'proj-1',
|
||||
name: 'my-proj',
|
||||
proxyMode: 'direct',
|
||||
servers: [{ id: 'ps-1', server: { id: 'srv-1', name: 'github' } }],
|
||||
});
|
||||
|
||||
vi.mocked(projectRepo.findById).mockResolvedValue(project);
|
||||
vi.mocked(serverRepo.findById).mockResolvedValue(srv);
|
||||
|
||||
const config = await service.generateMcpConfig('proj-1');
|
||||
expect(config.mcpServers['github']).toBeDefined();
|
||||
expect(config.mcpServers['github']?.command).toBe('npx');
|
||||
expect(config.mcpServers['github']?.args).toEqual(['-y', '@mcp/github']);
|
||||
});
|
||||
|
||||
it('generates direct mode config with SSE servers (URL-based)', async () => {
|
||||
const srv = makeServer({ id: 'srv-2', name: 'sse-server', transport: 'SSE' });
|
||||
const project = makeProject({
|
||||
id: 'proj-1',
|
||||
proxyMode: 'direct',
|
||||
servers: [{ id: 'ps-1', server: { id: 'srv-2', name: 'sse-server' } }],
|
||||
});
|
||||
|
||||
vi.mocked(projectRepo.findById).mockResolvedValue(project);
|
||||
vi.mocked(serverRepo.findById).mockResolvedValue(srv);
|
||||
|
||||
const config = await service.generateMcpConfig('proj-1');
|
||||
expect(config.mcpServers['sse-server']?.url).toBe('http://localhost:3100/api/v1/mcp/proxy/sse-server');
|
||||
expect(config.mcpServers['sse-server']?.command).toBeUndefined();
|
||||
});
|
||||
|
||||
it('generates filtered mode config (single mcplocal entry)', async () => {
|
||||
const project = makeProject({
|
||||
id: 'proj-1',
|
||||
name: 'filtered-proj',
|
||||
proxyMode: 'filtered',
|
||||
llmProvider: 'openai',
|
||||
servers: [{ id: 'ps-1', server: { id: 'srv-1', name: 'github' } }],
|
||||
});
|
||||
|
||||
@@ -344,14 +271,13 @@ describe('ProjectService', () => {
|
||||
|
||||
const config = await service.generateMcpConfig('proj-1');
|
||||
expect(Object.keys(config.mcpServers)).toHaveLength(1);
|
||||
expect(config.mcpServers['filtered-proj']?.url).toBe('http://localhost:3100/api/v1/mcp/proxy/project/filtered-proj');
|
||||
expect(config.mcpServers['my-proj']?.url).toBe('http://localhost:3100/api/v1/mcp/proxy/project/my-proj');
|
||||
});
|
||||
|
||||
it('resolves by name for mcp-config', async () => {
|
||||
const project = makeProject({
|
||||
id: 'proj-1',
|
||||
name: 'my-proj',
|
||||
proxyMode: 'direct',
|
||||
servers: [],
|
||||
});
|
||||
|
||||
@@ -359,27 +285,8 @@ describe('ProjectService', () => {
|
||||
vi.mocked(projectRepo.findByName).mockResolvedValue(project);
|
||||
|
||||
const config = await service.generateMcpConfig('my-proj');
|
||||
expect(config.mcpServers).toEqual({});
|
||||
});
|
||||
|
||||
it('includes env for STDIO servers', async () => {
|
||||
const srv = makeServer({
|
||||
id: 'srv-1',
|
||||
name: 'github',
|
||||
transport: 'STDIO',
|
||||
env: [{ name: 'GITHUB_TOKEN', value: 'tok123' }],
|
||||
});
|
||||
const project = makeProject({
|
||||
id: 'proj-1',
|
||||
proxyMode: 'direct',
|
||||
servers: [{ id: 'ps-1', server: { id: 'srv-1', name: 'github' } }],
|
||||
});
|
||||
|
||||
vi.mocked(projectRepo.findById).mockResolvedValue(project);
|
||||
vi.mocked(serverRepo.findById).mockResolvedValue(srv);
|
||||
|
||||
const config = await service.generateMcpConfig('proj-1');
|
||||
expect(config.mcpServers['github']?.env?.['GITHUB_TOKEN']).toBe('tok123');
|
||||
expect(Object.keys(config.mcpServers)).toHaveLength(1);
|
||||
expect(config.mcpServers['my-proj']?.url).toBe('http://localhost:3100/api/v1/mcp/proxy/project/my-proj');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,7 +48,6 @@ function makeProject(overrides: Partial<Project> = {}): Project {
|
||||
name: 'homeautomation',
|
||||
description: '',
|
||||
prompt: '',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: '',
|
||||
gated: true,
|
||||
llmProvider: null,
|
||||
|
||||
@@ -42,7 +42,6 @@ function makeProject(overrides: Partial<Project> = {}): Project {
|
||||
name: 'test-project',
|
||||
description: '',
|
||||
prompt: '',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: '',
|
||||
gated: true,
|
||||
llmProvider: null,
|
||||
|
||||
@@ -35,7 +35,6 @@ function makeProject(overrides: Partial<Project> = {}): Project {
|
||||
name: 'test-project',
|
||||
description: '',
|
||||
prompt: '',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: '',
|
||||
gated: true,
|
||||
llmProvider: null,
|
||||
|
||||
Reference in New Issue
Block a user