@rigour-labs/mcp 6.1.1 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway/runtime.d.ts +19 -0
- package/dist/gateway/runtime.js +81 -0
- package/dist/gateway/server.d.ts +1 -0
- package/dist/gateway/server.js +125 -0
- package/dist/index.js +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type { GatewayControlConfig } from '@rigour-labs/core';
|
|
3
|
+
export interface GatewayTool extends Tool {
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class DownstreamRegistry {
|
|
7
|
+
private readonly config;
|
|
8
|
+
private readonly cwd;
|
|
9
|
+
private readonly connected;
|
|
10
|
+
constructor(config: GatewayControlConfig, cwd: string);
|
|
11
|
+
connect(): Promise<void>;
|
|
12
|
+
listTools(): Promise<GatewayTool[]>;
|
|
13
|
+
call(serverName: string, toolName: string, args: Record<string, unknown>): Promise<CallToolResult>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function splitGatewayTool(name: string, configuredServers: string[]): {
|
|
17
|
+
server: string;
|
|
18
|
+
tool: string;
|
|
19
|
+
} | null;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
function exposedSchema(schema) {
|
|
4
|
+
const properties = schema.properties && typeof schema.properties === 'object' ? schema.properties : {};
|
|
5
|
+
return {
|
|
6
|
+
...schema,
|
|
7
|
+
properties: {
|
|
8
|
+
...properties,
|
|
9
|
+
_rigourCapability: {
|
|
10
|
+
type: 'string',
|
|
11
|
+
description: 'One-use capability id issued by the Rigour control plane',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export class DownstreamRegistry {
|
|
17
|
+
config;
|
|
18
|
+
cwd;
|
|
19
|
+
connected = new Map();
|
|
20
|
+
constructor(config, cwd) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.cwd = cwd;
|
|
23
|
+
}
|
|
24
|
+
async connect() {
|
|
25
|
+
try {
|
|
26
|
+
for (const [name, serverConfig] of Object.entries(this.config.servers)) {
|
|
27
|
+
const client = new Client({ name: 'rigour-mcp-gateway', version: '1.0.0' });
|
|
28
|
+
const transport = new StdioClientTransport({
|
|
29
|
+
command: serverConfig.command,
|
|
30
|
+
args: serverConfig.args,
|
|
31
|
+
env: { ...getDefaultEnvironment(), ...serverConfig.env },
|
|
32
|
+
cwd: this.cwd,
|
|
33
|
+
stderr: 'inherit',
|
|
34
|
+
});
|
|
35
|
+
await client.connect(transport);
|
|
36
|
+
this.connected.set(name, { client, transport, config: serverConfig });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
await this.close();
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async listTools() {
|
|
45
|
+
const tools = [];
|
|
46
|
+
for (const [serverName, downstream] of this.connected) {
|
|
47
|
+
const listed = await downstream.client.listTools();
|
|
48
|
+
for (const tool of listed.tools) {
|
|
49
|
+
if (!downstream.config.allow.includes('*') && !downstream.config.allow.includes(tool.name))
|
|
50
|
+
continue;
|
|
51
|
+
tools.push({
|
|
52
|
+
...tool,
|
|
53
|
+
name: `${serverName}__${tool.name}`,
|
|
54
|
+
description: `[via Rigour:${serverName}] ${tool.description ?? tool.name}`,
|
|
55
|
+
inputSchema: exposedSchema(tool.inputSchema),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return tools;
|
|
60
|
+
}
|
|
61
|
+
async call(serverName, toolName, args) {
|
|
62
|
+
const downstream = this.connected.get(serverName);
|
|
63
|
+
if (!downstream)
|
|
64
|
+
throw new Error(`Unknown downstream MCP server: ${serverName}`);
|
|
65
|
+
if (!downstream.config.allow.includes('*') && !downstream.config.allow.includes(toolName)) {
|
|
66
|
+
throw new Error(`Tool ${serverName}__${toolName} is not allowed by the trusted gateway configuration`);
|
|
67
|
+
}
|
|
68
|
+
return downstream.client.callTool({ name: toolName, arguments: args });
|
|
69
|
+
}
|
|
70
|
+
async close() {
|
|
71
|
+
await Promise.allSettled([...this.connected.values()].map(({ client }) => client.close()));
|
|
72
|
+
this.connected.clear();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function splitGatewayTool(name, configuredServers) {
|
|
76
|
+
const server = configuredServers.find((candidate) => name.startsWith(`${candidate}__`));
|
|
77
|
+
if (!server)
|
|
78
|
+
return null;
|
|
79
|
+
const tool = name.slice(server.length + 2);
|
|
80
|
+
return tool ? { server, tool } : null;
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runGatewayServer(cwd: string): Promise<void>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { appendExecutionReceipt, consumeTrustedGrant, getGatewayPolicyHash, loadGatewayConfig, normalizeMcpAction, } from '@rigour-labs/core';
|
|
5
|
+
import { DownstreamRegistry, splitGatewayTool } from './runtime.js';
|
|
6
|
+
function denied(message) {
|
|
7
|
+
return { isError: true, content: [{ type: 'text', text: `Rigour denied this action: ${message}` }] };
|
|
8
|
+
}
|
|
9
|
+
function failed(message) {
|
|
10
|
+
return { isError: true, content: [{ type: 'text', text: message }] };
|
|
11
|
+
}
|
|
12
|
+
function authorize(cwd, config, capabilityId, resource) {
|
|
13
|
+
if (!capabilityId)
|
|
14
|
+
return { ok: false, reason: 'Explicit capability is required' };
|
|
15
|
+
return consumeTrustedGrant(cwd, {
|
|
16
|
+
id: capabilityId,
|
|
17
|
+
subjectId: config.agentId,
|
|
18
|
+
taskId: config.taskId,
|
|
19
|
+
action: 'mcp.call',
|
|
20
|
+
resource,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function prepareCall(request, context) {
|
|
24
|
+
const target = splitGatewayTool(request.params.name, Object.keys(context.config.servers));
|
|
25
|
+
const rawArgs = (request.params.arguments ?? {});
|
|
26
|
+
const capabilityId = typeof rawArgs._rigourCapability === 'string' ? rawArgs._rigourCapability : undefined;
|
|
27
|
+
const args = { ...rawArgs };
|
|
28
|
+
delete args._rigourCapability;
|
|
29
|
+
const actionTarget = target ?? { server: 'gateway', tool: request.params.name };
|
|
30
|
+
const action = normalizeMcpAction({
|
|
31
|
+
actorId: context.config.agentId,
|
|
32
|
+
taskId: context.config.taskId,
|
|
33
|
+
server: actionTarget.server,
|
|
34
|
+
tool: actionTarget.tool,
|
|
35
|
+
args,
|
|
36
|
+
});
|
|
37
|
+
return { target, args, capabilityId, action };
|
|
38
|
+
}
|
|
39
|
+
function toolIsAllowed(context, target) {
|
|
40
|
+
const allowed = context.config.servers[target.server].allow;
|
|
41
|
+
return allowed.includes('*') || allowed.includes(target.tool);
|
|
42
|
+
}
|
|
43
|
+
function authorizationDecision(config, authorization) {
|
|
44
|
+
const mustDeny = config.mode === 'enforce' && !authorization.ok;
|
|
45
|
+
return {
|
|
46
|
+
mustDeny,
|
|
47
|
+
decision: (mustDeny ? 'deny' : 'allow'),
|
|
48
|
+
simulatedDecision: config.mode === 'observe' && !authorization.ok ? 'deny' : undefined,
|
|
49
|
+
reason: authorization.ok
|
|
50
|
+
? authorization.reason
|
|
51
|
+
: config.mode === 'observe'
|
|
52
|
+
? `Observe mode: would deny because ${authorization.reason}`
|
|
53
|
+
: authorization.reason,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function recordDenial(context, input) {
|
|
57
|
+
await appendExecutionReceipt(context.cwd, {
|
|
58
|
+
action: input.action,
|
|
59
|
+
mode: context.config.mode,
|
|
60
|
+
decision: 'deny',
|
|
61
|
+
reason: input.reason,
|
|
62
|
+
capabilityId: input.capabilityId,
|
|
63
|
+
policyHash: context.policyHash,
|
|
64
|
+
outcome: 'denied',
|
|
65
|
+
});
|
|
66
|
+
return denied(input.reason);
|
|
67
|
+
}
|
|
68
|
+
async function handleGatewayCall(request, context) {
|
|
69
|
+
const { target, args, capabilityId, action } = prepareCall(request, context);
|
|
70
|
+
if (!target)
|
|
71
|
+
return recordDenial(context, { action, capabilityId, reason: `Unknown gateway tool ${request.params.name}` });
|
|
72
|
+
if (!toolIsAllowed(context, target)) {
|
|
73
|
+
return recordDenial(context, {
|
|
74
|
+
action, capabilityId, reason: `Tool ${request.params.name} is not allowed by the trusted gateway configuration`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const authorization = await authorize(context.cwd, context.config, capabilityId, action.resource);
|
|
78
|
+
const { mustDeny, decision, simulatedDecision, reason } = authorizationDecision(context.config, authorization);
|
|
79
|
+
if (mustDeny)
|
|
80
|
+
return recordDenial(context, { action, capabilityId, reason });
|
|
81
|
+
let result;
|
|
82
|
+
try {
|
|
83
|
+
result = await context.downstream.call(target.server, target.tool, args);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const failure = `${reason}; downstream failed before returning a result: ${error?.message ?? String(error)}`;
|
|
87
|
+
await appendExecutionReceipt(context.cwd, {
|
|
88
|
+
action, mode: context.config.mode, decision, simulatedDecision, reason: failure,
|
|
89
|
+
capabilityId, policyHash: context.policyHash, outcome: 'failed',
|
|
90
|
+
});
|
|
91
|
+
return failed(failure);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const outcome = result.isError ? 'failed' : 'forwarded';
|
|
95
|
+
const receipt = await appendExecutionReceipt(context.cwd, {
|
|
96
|
+
action, mode: context.config.mode, decision, simulatedDecision,
|
|
97
|
+
reason: result.isError ? `${reason}; downstream returned an error result` : reason,
|
|
98
|
+
capabilityId, policyHash: context.policyHash, outcome,
|
|
99
|
+
});
|
|
100
|
+
return { ...result, _meta: { ...(result._meta ?? {}), rigourReceiptId: receipt.id } };
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
return failed(`Downstream action returned, but Rigour could not record its receipt: ${error?.message ?? String(error)}. Treat the external outcome as unknown and inspect the downstream system.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export async function runGatewayServer(cwd) {
|
|
107
|
+
const config = await loadGatewayConfig(cwd);
|
|
108
|
+
if (!config)
|
|
109
|
+
throw new Error('Gateway is not configured. Run `rigour firewall gateway-configure --config <file>`.');
|
|
110
|
+
const downstream = new DownstreamRegistry(config, cwd);
|
|
111
|
+
await downstream.connect();
|
|
112
|
+
const server = new Server({ name: 'rigour-mcp-gateway', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
113
|
+
const policyHash = getGatewayPolicyHash(cwd, config);
|
|
114
|
+
const context = { cwd, config, downstream, policyHash };
|
|
115
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: await downstream.listTools() }));
|
|
116
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => handleGatewayCall(request, context));
|
|
117
|
+
const shutdown = async () => {
|
|
118
|
+
await downstream.close();
|
|
119
|
+
await server.close();
|
|
120
|
+
};
|
|
121
|
+
process.once('SIGINT', shutdown);
|
|
122
|
+
process.once('SIGTERM', shutdown);
|
|
123
|
+
await server.connect(new StdioServerTransport());
|
|
124
|
+
console.error(`Rigour MCP gateway running in ${config.mode} mode for ${Object.keys(config.servers).length} downstream server(s)`);
|
|
125
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -467,6 +467,13 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
467
467
|
});
|
|
468
468
|
// ─── Start ────────────────────────────────────────────────────────
|
|
469
469
|
async function main() {
|
|
470
|
+
if (process.argv.includes('--gateway')) {
|
|
471
|
+
const repoFlag = process.argv.indexOf('--repo');
|
|
472
|
+
const cwd = repoFlag >= 0 && process.argv[repoFlag + 1] ? process.argv[repoFlag + 1] : process.cwd();
|
|
473
|
+
const { runGatewayServer } = await import('./gateway/server.js');
|
|
474
|
+
await runGatewayServer(cwd);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
470
477
|
const transport = new StdioServerTransport();
|
|
471
478
|
await server.connect(transport);
|
|
472
479
|
console.error("Rigour MCP server v4.0.0 running on stdio");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/mcp",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "MCP server + live dashboard for AI code governance — OWASP LLM Top 10 (10/10), real-time MCP App UI, 25+ security patterns, Bayesian learning Brain, hallucinated import detection, multi-agent governance. Works with Claude, Cursor, VS Code, ChatGPT, Goose, Windsurf. Industry presets for HIPAA, SOC2, FedRAMP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://rigour.run",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"execa": "^8.0.1",
|
|
63
63
|
"fs-extra": "^11.4.0",
|
|
64
64
|
"yaml": "^2.8.2",
|
|
65
|
-
"@rigour-labs/core": "6.
|
|
65
|
+
"@rigour-labs/core": "6.2.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^25.0.3",
|