feat: granular RBAC with resource/operation bindings, users, groups
- Replace admin role with granular roles: view, create, delete, edit, run - Two binding types: resource bindings (role+resource+optional name) and operation bindings (role:run + action like backup, logs, impersonate) - Name-scoped resource bindings for per-instance access control - Remove role from project members (all permissions via RBAC) - Add users, groups, RBAC CRUD endpoints and CLI commands - describe user/group shows all RBAC access (direct + inherited) - create rbac supports --subject, --binding, --operation flags - Backup/restore handles users, groups, RBAC definitions - mcplocal project-based MCP endpoint discovery - Full test coverage for all new functionality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,10 +11,17 @@ function makeServer(overrides: Partial<McpServer> = {}): McpServer {
|
||||
dockerImage: null,
|
||||
transport: 'STDIO',
|
||||
repositoryUrl: null,
|
||||
externalUrl: null,
|
||||
command: null,
|
||||
containerPort: null,
|
||||
replicas: 1,
|
||||
env: [],
|
||||
healthCheck: null,
|
||||
version: 1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
templateName: null,
|
||||
templateVersion: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -25,7 +32,7 @@ describe('generateMcpConfig', () => {
|
||||
expect(result).toEqual({ mcpServers: {} });
|
||||
});
|
||||
|
||||
it('generates config for a single server', () => {
|
||||
it('generates config for a single STDIO server', () => {
|
||||
const result = generateMcpConfig([
|
||||
{ server: makeServer(), resolvedEnv: {} },
|
||||
]);
|
||||
@@ -34,7 +41,7 @@ describe('generateMcpConfig', () => {
|
||||
expect(result.mcpServers['slack']?.args).toEqual(['-y', '@anthropic/slack-mcp']);
|
||||
});
|
||||
|
||||
it('includes resolved env when present', () => {
|
||||
it('includes resolved env when present for STDIO server', () => {
|
||||
const result = generateMcpConfig([
|
||||
{ server: makeServer(), resolvedEnv: { SLACK_TEAM_ID: 'T123' } },
|
||||
]);
|
||||
@@ -67,4 +74,35 @@ describe('generateMcpConfig', () => {
|
||||
]);
|
||||
expect(result.mcpServers['slack']?.args).toEqual(['-y', 'slack']);
|
||||
});
|
||||
|
||||
it('generates URL-based config for SSE servers', () => {
|
||||
const server = makeServer({ name: 'sse-server', transport: 'SSE' });
|
||||
const result = generateMcpConfig([
|
||||
{ server, resolvedEnv: { TOKEN: 'abc' } },
|
||||
]);
|
||||
const config = result.mcpServers['sse-server'];
|
||||
expect(config?.url).toBe('http://localhost:3100/api/v1/mcp/proxy/sse-server');
|
||||
expect(config?.command).toBeUndefined();
|
||||
expect(config?.args).toBeUndefined();
|
||||
expect(config?.env).toBeUndefined();
|
||||
});
|
||||
|
||||
it('generates URL-based config for STREAMABLE_HTTP servers', () => {
|
||||
const server = makeServer({ name: 'stream-server', transport: 'STREAMABLE_HTTP' });
|
||||
const result = generateMcpConfig([
|
||||
{ server, resolvedEnv: {} },
|
||||
]);
|
||||
const config = result.mcpServers['stream-server'];
|
||||
expect(config?.url).toBe('http://localhost:3100/api/v1/mcp/proxy/stream-server');
|
||||
expect(config?.command).toBeUndefined();
|
||||
});
|
||||
|
||||
it('mixes STDIO and SSE servers correctly', () => {
|
||||
const result = generateMcpConfig([
|
||||
{ server: makeServer({ name: 'stdio-srv', transport: 'STDIO' }), resolvedEnv: {} },
|
||||
{ server: makeServer({ name: 'sse-srv', transport: 'SSE' }), resolvedEnv: {} },
|
||||
]);
|
||||
expect(result.mcpServers['stdio-srv']?.command).toBe('npx');
|
||||
expect(result.mcpServers['sse-srv']?.url).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user