sovr-mcp-proxy 6.0.1 → 7.1.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/LICENSE +54 -19
- package/README.md +387 -164
- package/dist/auditDashboard.d.mts +208 -0
- package/dist/auditDashboard.d.ts +208 -0
- package/dist/auditDashboard.js +398 -0
- package/dist/auditDashboard.mjs +370 -0
- package/dist/cli.js +1553 -24
- package/dist/cli.mjs +185 -18
- package/dist/commandNormalizer.d.mts +95 -0
- package/dist/commandNormalizer.d.ts +95 -0
- package/dist/commandNormalizer.js +365 -0
- package/dist/commandNormalizer.mjs +336 -0
- package/dist/hooksAdapter.d.mts +122 -0
- package/dist/hooksAdapter.d.ts +122 -0
- package/dist/hooksAdapter.js +321 -0
- package/dist/hooksAdapter.mjs +291 -0
- package/dist/index.js +1065 -2
- package/dist/index.mjs +98 -2
- package/dist/mcpProxyInterceptor.d.mts +256 -0
- package/dist/mcpProxyInterceptor.d.ts +256 -0
- package/dist/mcpProxyInterceptor.js +579 -0
- package/dist/mcpProxyInterceptor.mjs +552 -0
- package/dist/semanticAnalyzer.d.mts +133 -0
- package/dist/semanticAnalyzer.d.ts +133 -0
- package/dist/semanticAnalyzer.js +701 -0
- package/dist/semanticAnalyzer.mjs +674 -0
- package/dist/teamPolicyManager.d.mts +202 -0
- package/dist/teamPolicyManager.d.ts +202 -0
- package/dist/teamPolicyManager.js +529 -0
- package/dist/teamPolicyManager.mjs +502 -0
- package/dist/toolReplacement.d.mts +108 -0
- package/dist/toolReplacement.d.ts +108 -0
- package/dist/toolReplacement.js +234 -0
- package/dist/toolReplacement.mjs +204 -0
- package/dist/whitelistEngine.d.mts +167 -0
- package/dist/whitelistEngine.d.ts +167 -0
- package/dist/whitelistEngine.js +435 -0
- package/dist/whitelistEngine.mjs +403 -0
- package/package.json +46 -41
- package/server.json +0 -14
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
7
|
import { EventEmitter } from "events";
|
|
8
|
-
var PROXY_VERSION = "
|
|
8
|
+
var PROXY_VERSION = "7.0.0";
|
|
9
9
|
var SOVR_MIN_VERSION_URL = "https://api.sovr.inc/api/sovr/v1/version/check";
|
|
10
10
|
var SOVR_DASHBOARD_URL = "https://sovr.inc/dashboard/api-keys";
|
|
11
11
|
function validateApiKey(key) {
|
|
@@ -823,11 +823,15 @@ var McpProxy = class extends EventEmitter {
|
|
|
823
823
|
};
|
|
824
824
|
async function cli(args) {
|
|
825
825
|
const { PolicyEngine, DEFAULT_RULES } = await import("./engine-DWKHAJGE.mjs");
|
|
826
|
+
const { transformToolList, shouldIntercept, parseMode } = await import("./toolReplacement.mjs");
|
|
827
|
+
const { normalize: normalizeCommand, summarize: summarizeNormalization } = await import("./commandNormalizer.mjs");
|
|
826
828
|
let upstreamCmd = "";
|
|
827
829
|
let upstreamArgs = [];
|
|
828
830
|
let rulesFile = null;
|
|
829
831
|
let verbose = false;
|
|
830
832
|
let startupTimeoutMs = 3e4;
|
|
833
|
+
let mode = "enforce";
|
|
834
|
+
let whitelistPreset = null;
|
|
831
835
|
for (let i = 0; i < args.length; i++) {
|
|
832
836
|
switch (args[i]) {
|
|
833
837
|
case "--upstream":
|
|
@@ -849,14 +853,39 @@ async function cli(args) {
|
|
|
849
853
|
case "-t":
|
|
850
854
|
startupTimeoutMs = parseInt(args[++i] ?? "30000", 10);
|
|
851
855
|
break;
|
|
856
|
+
case "--mode":
|
|
857
|
+
case "-m":
|
|
858
|
+
mode = args[++i] ?? "enforce";
|
|
859
|
+
break;
|
|
860
|
+
case "--whitelist":
|
|
861
|
+
case "-w":
|
|
862
|
+
whitelistPreset = args[++i] ?? null;
|
|
863
|
+
break;
|
|
864
|
+
default: {
|
|
865
|
+
const modeMatch = args[i]?.match(/^--mode=(.+)$/);
|
|
866
|
+
if (modeMatch) {
|
|
867
|
+
mode = modeMatch[1];
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
const wlMatch = args[i]?.match(/^--whitelist=(.+)$/);
|
|
871
|
+
if (wlMatch) {
|
|
872
|
+
whitelistPreset = wlMatch[1];
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
852
876
|
}
|
|
853
877
|
}
|
|
854
878
|
if (!upstreamCmd) {
|
|
855
879
|
process.stderr.write(
|
|
856
|
-
'Usage: sovr-mcp-proxy --upstream "command args..." [--
|
|
880
|
+
'Usage: sovr-mcp-proxy --upstream "command args..." [--mode=exclusive|enforce|advisory|monitor] [--whitelist=preset|path] [--verbose]\n'
|
|
857
881
|
);
|
|
858
882
|
process.exit(1);
|
|
859
883
|
}
|
|
884
|
+
if (!["exclusive", "enforce", "advisory", "monitor"].includes(mode)) {
|
|
885
|
+
process.stderr.write(`[SOVR] Invalid mode: ${mode}. Must be: exclusive|enforce|advisory|monitor
|
|
886
|
+
`);
|
|
887
|
+
process.exit(1);
|
|
888
|
+
}
|
|
860
889
|
let rules = DEFAULT_RULES;
|
|
861
890
|
if (rulesFile) {
|
|
862
891
|
const fs = await import("fs");
|
|
@@ -864,6 +893,26 @@ async function cli(args) {
|
|
|
864
893
|
const parsed = JSON.parse(content);
|
|
865
894
|
rules = parsed.rules ?? parsed;
|
|
866
895
|
}
|
|
896
|
+
const { WhitelistEngine, PRESETS: WL_PRESETS } = await import("./whitelistEngine.mjs");
|
|
897
|
+
let whitelist = null;
|
|
898
|
+
if (whitelistPreset) {
|
|
899
|
+
const presetNames = Object.keys(WL_PRESETS);
|
|
900
|
+
if (presetNames.includes(whitelistPreset)) {
|
|
901
|
+
whitelist = new WhitelistEngine(WL_PRESETS[whitelistPreset]);
|
|
902
|
+
} else {
|
|
903
|
+
try {
|
|
904
|
+
const fs = await import("fs");
|
|
905
|
+
const content = fs.readFileSync(whitelistPreset, "utf-8");
|
|
906
|
+
const parsed = JSON.parse(content);
|
|
907
|
+
whitelist = new WhitelistEngine(parsed);
|
|
908
|
+
} catch (err) {
|
|
909
|
+
process.stderr.write(`[SOVR] Failed to load whitelist from ${whitelistPreset}: ${err.message}
|
|
910
|
+
`);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const validatedMode = parseMode(mode);
|
|
867
916
|
const engine = new PolicyEngine({
|
|
868
917
|
rules,
|
|
869
918
|
audit_log: true,
|
|
@@ -876,6 +925,24 @@ async function cli(args) {
|
|
|
876
925
|
}
|
|
877
926
|
}
|
|
878
927
|
});
|
|
928
|
+
process.stderr.write(`
|
|
929
|
+
`);
|
|
930
|
+
process.stderr.write(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
931
|
+
`);
|
|
932
|
+
process.stderr.write(` \u2551 SOVR MCP Proxy v${PROXY_VERSION} \u2551
|
|
933
|
+
`);
|
|
934
|
+
process.stderr.write(` \u2551 Mode: ${mode.toUpperCase().padEnd(40)}\u2551
|
|
935
|
+
`);
|
|
936
|
+
if (whitelist) {
|
|
937
|
+
process.stderr.write(` \u2551 Whitelist: ${(whitelistPreset ?? "custom").padEnd(35)}\u2551
|
|
938
|
+
`);
|
|
939
|
+
}
|
|
940
|
+
process.stderr.write(` \u2551 Upstream: ${upstreamCmd.substring(0, 36).padEnd(36)}\u2551
|
|
941
|
+
`);
|
|
942
|
+
process.stderr.write(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
943
|
+
`);
|
|
944
|
+
process.stderr.write(`
|
|
945
|
+
`);
|
|
879
946
|
const proxy = new McpProxy({
|
|
880
947
|
engine,
|
|
881
948
|
upstream: { command: upstreamCmd, args: upstreamArgs },
|
|
@@ -892,8 +959,37 @@ async function cli(args) {
|
|
|
892
959
|
`[ESCALATED] ${info.toolName}: ${info.decision.reason}
|
|
893
960
|
`
|
|
894
961
|
);
|
|
962
|
+
},
|
|
963
|
+
onIntercept: (info) => {
|
|
964
|
+
if (info.arguments?.command && typeof info.arguments.command === "string") {
|
|
965
|
+
const normalized = normalizeCommand(info.arguments.command);
|
|
966
|
+
if (verbose) {
|
|
967
|
+
process.stderr.write(`[SOVR] Normalized: ${summarizeNormalization(normalized)}
|
|
968
|
+
`);
|
|
969
|
+
if (normalized.suspicious) {
|
|
970
|
+
process.stderr.write(`[SOVR] \u26A0 Suspicious: ${normalized.suspicion_reasons.join(", ")}
|
|
971
|
+
`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (whitelist && info.arguments?.command && typeof info.arguments.command === "string") {
|
|
976
|
+
const wlResult = whitelist.evaluate(info.arguments.command);
|
|
977
|
+
if (!wlResult.allowed && (validatedMode === "enforce" || validatedMode === "exclusive")) {
|
|
978
|
+
if (verbose) {
|
|
979
|
+
process.stderr.write(`[SOVR] Whitelist DENIED: ${wlResult.reason}
|
|
980
|
+
`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
895
984
|
}
|
|
896
985
|
});
|
|
986
|
+
if (validatedMode === "exclusive") {
|
|
987
|
+
proxy.on("intercept", () => {
|
|
988
|
+
if (verbose) {
|
|
989
|
+
process.stderr.write("[SOVR] Exclusive mode: all tool calls routed through SOVR\n");
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
}
|
|
897
993
|
await proxy.start();
|
|
898
994
|
}
|
|
899
995
|
var index_default = McpProxy;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOVR MCP Proxy Interceptor — Universal Tool Call Interception
|
|
3
|
+
*
|
|
4
|
+
* P1-1: SOVR sits between the MCP client (LLM) and the MCP server (tools),
|
|
5
|
+
* intercepting ALL tool calls regardless of transport (stdio/SSE/HTTP).
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* [LLM Client] → [SOVR Proxy] → [Upstream MCP Server]
|
|
9
|
+
* ↓
|
|
10
|
+
* [Policy Engine]
|
|
11
|
+
* [Audit Trail]
|
|
12
|
+
* [Rate Limiter]
|
|
13
|
+
* [Semantic Analyzer]
|
|
14
|
+
*
|
|
15
|
+
* Unlike Tool Replacement (which replaces tools), this module transparently
|
|
16
|
+
* proxies ALL tools and applies policies at the protocol level.
|
|
17
|
+
*/
|
|
18
|
+
interface InterceptorConfig {
|
|
19
|
+
/** Policy mode: block | warn | audit-only */
|
|
20
|
+
mode: 'block' | 'warn' | 'audit';
|
|
21
|
+
/** Maximum concurrent tool calls */
|
|
22
|
+
maxConcurrent: number;
|
|
23
|
+
/** Per-tool rate limits (calls per minute) */
|
|
24
|
+
rateLimits: Record<string, number>;
|
|
25
|
+
/** Global rate limit (calls per minute) */
|
|
26
|
+
globalRateLimit: number;
|
|
27
|
+
/** Tool-specific policies */
|
|
28
|
+
toolPolicies: ToolPolicy[];
|
|
29
|
+
/** Enable semantic analysis for tool arguments */
|
|
30
|
+
enableSemanticAnalysis: boolean;
|
|
31
|
+
/** Callback for human approval */
|
|
32
|
+
onApprovalRequired?: (request: InterceptedCall) => Promise<ApprovalResult>;
|
|
33
|
+
/** Callback for audit events */
|
|
34
|
+
onAuditEvent?: (event: AuditEvent) => void;
|
|
35
|
+
/** Timeout for upstream tool calls (ms) */
|
|
36
|
+
upstreamTimeout: number;
|
|
37
|
+
/** Max retries for transient failures */
|
|
38
|
+
maxRetries: number;
|
|
39
|
+
}
|
|
40
|
+
interface ToolPolicy {
|
|
41
|
+
/** Tool name pattern (glob) */
|
|
42
|
+
toolPattern: string;
|
|
43
|
+
/** Action to take */
|
|
44
|
+
action: 'allow' | 'block' | 'require-approval' | 'rate-limit' | 'transform';
|
|
45
|
+
/** Conditions for this policy */
|
|
46
|
+
conditions?: PolicyCondition[];
|
|
47
|
+
/** Argument transformations (for 'transform' action) */
|
|
48
|
+
transforms?: ArgumentTransform[];
|
|
49
|
+
/** Priority (higher = evaluated first) */
|
|
50
|
+
priority: number;
|
|
51
|
+
/** Human-readable description */
|
|
52
|
+
description: string;
|
|
53
|
+
}
|
|
54
|
+
interface PolicyCondition {
|
|
55
|
+
/** Field path in tool arguments (dot notation) */
|
|
56
|
+
field: string;
|
|
57
|
+
/** Operator */
|
|
58
|
+
operator: 'contains' | 'matches' | 'equals' | 'gt' | 'lt' | 'exists' | 'not-exists';
|
|
59
|
+
/** Value to compare against */
|
|
60
|
+
value: string | number | boolean;
|
|
61
|
+
}
|
|
62
|
+
interface ArgumentTransform {
|
|
63
|
+
/** Field path to transform */
|
|
64
|
+
field: string;
|
|
65
|
+
/** Transform type */
|
|
66
|
+
type: 'redact' | 'mask' | 'replace' | 'remove';
|
|
67
|
+
/** Replacement value (for 'replace') */
|
|
68
|
+
replacement?: string;
|
|
69
|
+
}
|
|
70
|
+
interface InterceptedCall {
|
|
71
|
+
/** Unique call ID */
|
|
72
|
+
id: string;
|
|
73
|
+
/** Timestamp */
|
|
74
|
+
timestamp: number;
|
|
75
|
+
/** Tool name */
|
|
76
|
+
toolName: string;
|
|
77
|
+
/** Original arguments */
|
|
78
|
+
arguments: Record<string, unknown>;
|
|
79
|
+
/** Matched policies */
|
|
80
|
+
matchedPolicies: ToolPolicy[];
|
|
81
|
+
/** Decision */
|
|
82
|
+
decision: InterceptDecision;
|
|
83
|
+
/** Semantic analysis result (if enabled) */
|
|
84
|
+
semanticResult?: SemanticAnalysisResult;
|
|
85
|
+
}
|
|
86
|
+
interface InterceptDecision {
|
|
87
|
+
/** Final action */
|
|
88
|
+
action: 'allow' | 'block' | 'require-approval' | 'transform';
|
|
89
|
+
/** Reason for decision */
|
|
90
|
+
reason: string;
|
|
91
|
+
/** Policy that triggered this decision */
|
|
92
|
+
triggeringPolicy?: ToolPolicy;
|
|
93
|
+
/** Risk score (0-100) */
|
|
94
|
+
riskScore: number;
|
|
95
|
+
/** Transformed arguments (if action is 'transform') */
|
|
96
|
+
transformedArguments?: Record<string, unknown>;
|
|
97
|
+
}
|
|
98
|
+
interface SemanticAnalysisResult {
|
|
99
|
+
/** Detected intent */
|
|
100
|
+
intent: string;
|
|
101
|
+
/** Risk category */
|
|
102
|
+
riskCategory: 'safe' | 'suspicious' | 'dangerous' | 'critical';
|
|
103
|
+
/** Confidence (0-1) */
|
|
104
|
+
confidence: number;
|
|
105
|
+
/** Explanation */
|
|
106
|
+
explanation: string;
|
|
107
|
+
}
|
|
108
|
+
interface ApprovalResult {
|
|
109
|
+
approved: boolean;
|
|
110
|
+
approver?: string;
|
|
111
|
+
reason?: string;
|
|
112
|
+
timestamp: number;
|
|
113
|
+
}
|
|
114
|
+
interface AuditEvent {
|
|
115
|
+
id: string;
|
|
116
|
+
timestamp: number;
|
|
117
|
+
type: 'intercept' | 'allow' | 'block' | 'approval-requested' | 'approval-granted' | 'approval-denied' | 'error' | 'rate-limited';
|
|
118
|
+
toolName: string;
|
|
119
|
+
arguments: Record<string, unknown>;
|
|
120
|
+
decision: InterceptDecision;
|
|
121
|
+
duration?: number;
|
|
122
|
+
error?: string;
|
|
123
|
+
upstreamResult?: unknown;
|
|
124
|
+
}
|
|
125
|
+
interface InterceptorStats {
|
|
126
|
+
totalCalls: number;
|
|
127
|
+
allowed: number;
|
|
128
|
+
blocked: number;
|
|
129
|
+
approvalRequested: number;
|
|
130
|
+
rateLimited: number;
|
|
131
|
+
errors: number;
|
|
132
|
+
avgLatencyMs: number;
|
|
133
|
+
callsByTool: Record<string, number>;
|
|
134
|
+
blocksByPolicy: Record<string, number>;
|
|
135
|
+
riskDistribution: {
|
|
136
|
+
safe: number;
|
|
137
|
+
suspicious: number;
|
|
138
|
+
dangerous: number;
|
|
139
|
+
critical: number;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
declare class McpProxyInterceptor {
|
|
143
|
+
private config;
|
|
144
|
+
private rateLimiter;
|
|
145
|
+
private stats;
|
|
146
|
+
private auditLog;
|
|
147
|
+
private maxAuditLogSize;
|
|
148
|
+
private activeCalls;
|
|
149
|
+
private cleanupInterval;
|
|
150
|
+
constructor(config?: Partial<InterceptorConfig>);
|
|
151
|
+
/**
|
|
152
|
+
* Intercept a tool call — the core entry point.
|
|
153
|
+
* Returns the decision and optionally transformed arguments.
|
|
154
|
+
*/
|
|
155
|
+
intercept(toolName: string, args: Record<string, unknown>): Promise<InterceptedCall>;
|
|
156
|
+
/**
|
|
157
|
+
* Wrap an upstream tool call with interception.
|
|
158
|
+
* If allowed, executes the upstream function; if blocked, returns error.
|
|
159
|
+
*/
|
|
160
|
+
wrapToolCall<T>(toolName: string, args: Record<string, unknown>, upstream: (args: Record<string, unknown>) => Promise<T>): Promise<{
|
|
161
|
+
result?: T;
|
|
162
|
+
intercepted: InterceptedCall;
|
|
163
|
+
error?: string;
|
|
164
|
+
}>;
|
|
165
|
+
/** Get current statistics */
|
|
166
|
+
getStats(): InterceptorStats;
|
|
167
|
+
/** Get recent audit log */
|
|
168
|
+
getAuditLog(limit?: number): AuditEvent[];
|
|
169
|
+
/** Export audit log as CSV */
|
|
170
|
+
exportAuditCSV(): string;
|
|
171
|
+
/** Add a policy at runtime */
|
|
172
|
+
addPolicy(policy: ToolPolicy): void;
|
|
173
|
+
/** Remove policies matching a pattern */
|
|
174
|
+
removePolicies(toolPattern: string): number;
|
|
175
|
+
/** Reset statistics */
|
|
176
|
+
resetStats(): void;
|
|
177
|
+
/** Cleanup resources */
|
|
178
|
+
destroy(): void;
|
|
179
|
+
private emitAudit;
|
|
180
|
+
private trimAuditLog;
|
|
181
|
+
}
|
|
182
|
+
declare const PRESET_POLICIES: {
|
|
183
|
+
/** Block all destructive file operations */
|
|
184
|
+
noDestructiveFiles: {
|
|
185
|
+
toolPattern: string;
|
|
186
|
+
action: "block";
|
|
187
|
+
conditions: {
|
|
188
|
+
field: string;
|
|
189
|
+
operator: "matches";
|
|
190
|
+
value: string;
|
|
191
|
+
}[];
|
|
192
|
+
priority: number;
|
|
193
|
+
description: string;
|
|
194
|
+
};
|
|
195
|
+
/** Block all database write operations */
|
|
196
|
+
readOnlyDatabase: {
|
|
197
|
+
toolPattern: string;
|
|
198
|
+
action: "block";
|
|
199
|
+
conditions: {
|
|
200
|
+
field: string;
|
|
201
|
+
operator: "matches";
|
|
202
|
+
value: string;
|
|
203
|
+
}[];
|
|
204
|
+
priority: number;
|
|
205
|
+
description: string;
|
|
206
|
+
};
|
|
207
|
+
/** Require approval for payment operations */
|
|
208
|
+
approvePayments: {
|
|
209
|
+
toolPattern: string;
|
|
210
|
+
action: "require-approval";
|
|
211
|
+
priority: number;
|
|
212
|
+
description: string;
|
|
213
|
+
};
|
|
214
|
+
/** Redact secrets in tool arguments */
|
|
215
|
+
redactSecrets: {
|
|
216
|
+
toolPattern: string;
|
|
217
|
+
action: "transform";
|
|
218
|
+
conditions: {
|
|
219
|
+
field: string;
|
|
220
|
+
operator: "matches";
|
|
221
|
+
value: string;
|
|
222
|
+
}[];
|
|
223
|
+
transforms: ({
|
|
224
|
+
field: string;
|
|
225
|
+
type: "redact";
|
|
226
|
+
} | {
|
|
227
|
+
field: string;
|
|
228
|
+
type: "mask";
|
|
229
|
+
})[];
|
|
230
|
+
priority: number;
|
|
231
|
+
description: string;
|
|
232
|
+
};
|
|
233
|
+
/** Rate limit shell commands */
|
|
234
|
+
rateLimitShell: {
|
|
235
|
+
toolPattern: string;
|
|
236
|
+
action: "rate-limit";
|
|
237
|
+
priority: number;
|
|
238
|
+
description: string;
|
|
239
|
+
};
|
|
240
|
+
/** Allow read-only operations */
|
|
241
|
+
allowReadOnly: {
|
|
242
|
+
toolPattern: string;
|
|
243
|
+
action: "allow";
|
|
244
|
+
conditions: {
|
|
245
|
+
field: string;
|
|
246
|
+
operator: "matches";
|
|
247
|
+
value: string;
|
|
248
|
+
}[];
|
|
249
|
+
priority: number;
|
|
250
|
+
description: string;
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
/** Create a pre-configured interceptor with common security policies */
|
|
254
|
+
declare function createSecureInterceptor(overrides?: Partial<InterceptorConfig>): McpProxyInterceptor;
|
|
255
|
+
|
|
256
|
+
export { type ApprovalResult, type ArgumentTransform, type AuditEvent, type InterceptDecision, type InterceptedCall, type InterceptorConfig, type InterceptorStats, McpProxyInterceptor, PRESET_POLICIES, type PolicyCondition, type SemanticAnalysisResult, type ToolPolicy, createSecureInterceptor };
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOVR MCP Proxy Interceptor — Universal Tool Call Interception
|
|
3
|
+
*
|
|
4
|
+
* P1-1: SOVR sits between the MCP client (LLM) and the MCP server (tools),
|
|
5
|
+
* intercepting ALL tool calls regardless of transport (stdio/SSE/HTTP).
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* [LLM Client] → [SOVR Proxy] → [Upstream MCP Server]
|
|
9
|
+
* ↓
|
|
10
|
+
* [Policy Engine]
|
|
11
|
+
* [Audit Trail]
|
|
12
|
+
* [Rate Limiter]
|
|
13
|
+
* [Semantic Analyzer]
|
|
14
|
+
*
|
|
15
|
+
* Unlike Tool Replacement (which replaces tools), this module transparently
|
|
16
|
+
* proxies ALL tools and applies policies at the protocol level.
|
|
17
|
+
*/
|
|
18
|
+
interface InterceptorConfig {
|
|
19
|
+
/** Policy mode: block | warn | audit-only */
|
|
20
|
+
mode: 'block' | 'warn' | 'audit';
|
|
21
|
+
/** Maximum concurrent tool calls */
|
|
22
|
+
maxConcurrent: number;
|
|
23
|
+
/** Per-tool rate limits (calls per minute) */
|
|
24
|
+
rateLimits: Record<string, number>;
|
|
25
|
+
/** Global rate limit (calls per minute) */
|
|
26
|
+
globalRateLimit: number;
|
|
27
|
+
/** Tool-specific policies */
|
|
28
|
+
toolPolicies: ToolPolicy[];
|
|
29
|
+
/** Enable semantic analysis for tool arguments */
|
|
30
|
+
enableSemanticAnalysis: boolean;
|
|
31
|
+
/** Callback for human approval */
|
|
32
|
+
onApprovalRequired?: (request: InterceptedCall) => Promise<ApprovalResult>;
|
|
33
|
+
/** Callback for audit events */
|
|
34
|
+
onAuditEvent?: (event: AuditEvent) => void;
|
|
35
|
+
/** Timeout for upstream tool calls (ms) */
|
|
36
|
+
upstreamTimeout: number;
|
|
37
|
+
/** Max retries for transient failures */
|
|
38
|
+
maxRetries: number;
|
|
39
|
+
}
|
|
40
|
+
interface ToolPolicy {
|
|
41
|
+
/** Tool name pattern (glob) */
|
|
42
|
+
toolPattern: string;
|
|
43
|
+
/** Action to take */
|
|
44
|
+
action: 'allow' | 'block' | 'require-approval' | 'rate-limit' | 'transform';
|
|
45
|
+
/** Conditions for this policy */
|
|
46
|
+
conditions?: PolicyCondition[];
|
|
47
|
+
/** Argument transformations (for 'transform' action) */
|
|
48
|
+
transforms?: ArgumentTransform[];
|
|
49
|
+
/** Priority (higher = evaluated first) */
|
|
50
|
+
priority: number;
|
|
51
|
+
/** Human-readable description */
|
|
52
|
+
description: string;
|
|
53
|
+
}
|
|
54
|
+
interface PolicyCondition {
|
|
55
|
+
/** Field path in tool arguments (dot notation) */
|
|
56
|
+
field: string;
|
|
57
|
+
/** Operator */
|
|
58
|
+
operator: 'contains' | 'matches' | 'equals' | 'gt' | 'lt' | 'exists' | 'not-exists';
|
|
59
|
+
/** Value to compare against */
|
|
60
|
+
value: string | number | boolean;
|
|
61
|
+
}
|
|
62
|
+
interface ArgumentTransform {
|
|
63
|
+
/** Field path to transform */
|
|
64
|
+
field: string;
|
|
65
|
+
/** Transform type */
|
|
66
|
+
type: 'redact' | 'mask' | 'replace' | 'remove';
|
|
67
|
+
/** Replacement value (for 'replace') */
|
|
68
|
+
replacement?: string;
|
|
69
|
+
}
|
|
70
|
+
interface InterceptedCall {
|
|
71
|
+
/** Unique call ID */
|
|
72
|
+
id: string;
|
|
73
|
+
/** Timestamp */
|
|
74
|
+
timestamp: number;
|
|
75
|
+
/** Tool name */
|
|
76
|
+
toolName: string;
|
|
77
|
+
/** Original arguments */
|
|
78
|
+
arguments: Record<string, unknown>;
|
|
79
|
+
/** Matched policies */
|
|
80
|
+
matchedPolicies: ToolPolicy[];
|
|
81
|
+
/** Decision */
|
|
82
|
+
decision: InterceptDecision;
|
|
83
|
+
/** Semantic analysis result (if enabled) */
|
|
84
|
+
semanticResult?: SemanticAnalysisResult;
|
|
85
|
+
}
|
|
86
|
+
interface InterceptDecision {
|
|
87
|
+
/** Final action */
|
|
88
|
+
action: 'allow' | 'block' | 'require-approval' | 'transform';
|
|
89
|
+
/** Reason for decision */
|
|
90
|
+
reason: string;
|
|
91
|
+
/** Policy that triggered this decision */
|
|
92
|
+
triggeringPolicy?: ToolPolicy;
|
|
93
|
+
/** Risk score (0-100) */
|
|
94
|
+
riskScore: number;
|
|
95
|
+
/** Transformed arguments (if action is 'transform') */
|
|
96
|
+
transformedArguments?: Record<string, unknown>;
|
|
97
|
+
}
|
|
98
|
+
interface SemanticAnalysisResult {
|
|
99
|
+
/** Detected intent */
|
|
100
|
+
intent: string;
|
|
101
|
+
/** Risk category */
|
|
102
|
+
riskCategory: 'safe' | 'suspicious' | 'dangerous' | 'critical';
|
|
103
|
+
/** Confidence (0-1) */
|
|
104
|
+
confidence: number;
|
|
105
|
+
/** Explanation */
|
|
106
|
+
explanation: string;
|
|
107
|
+
}
|
|
108
|
+
interface ApprovalResult {
|
|
109
|
+
approved: boolean;
|
|
110
|
+
approver?: string;
|
|
111
|
+
reason?: string;
|
|
112
|
+
timestamp: number;
|
|
113
|
+
}
|
|
114
|
+
interface AuditEvent {
|
|
115
|
+
id: string;
|
|
116
|
+
timestamp: number;
|
|
117
|
+
type: 'intercept' | 'allow' | 'block' | 'approval-requested' | 'approval-granted' | 'approval-denied' | 'error' | 'rate-limited';
|
|
118
|
+
toolName: string;
|
|
119
|
+
arguments: Record<string, unknown>;
|
|
120
|
+
decision: InterceptDecision;
|
|
121
|
+
duration?: number;
|
|
122
|
+
error?: string;
|
|
123
|
+
upstreamResult?: unknown;
|
|
124
|
+
}
|
|
125
|
+
interface InterceptorStats {
|
|
126
|
+
totalCalls: number;
|
|
127
|
+
allowed: number;
|
|
128
|
+
blocked: number;
|
|
129
|
+
approvalRequested: number;
|
|
130
|
+
rateLimited: number;
|
|
131
|
+
errors: number;
|
|
132
|
+
avgLatencyMs: number;
|
|
133
|
+
callsByTool: Record<string, number>;
|
|
134
|
+
blocksByPolicy: Record<string, number>;
|
|
135
|
+
riskDistribution: {
|
|
136
|
+
safe: number;
|
|
137
|
+
suspicious: number;
|
|
138
|
+
dangerous: number;
|
|
139
|
+
critical: number;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
declare class McpProxyInterceptor {
|
|
143
|
+
private config;
|
|
144
|
+
private rateLimiter;
|
|
145
|
+
private stats;
|
|
146
|
+
private auditLog;
|
|
147
|
+
private maxAuditLogSize;
|
|
148
|
+
private activeCalls;
|
|
149
|
+
private cleanupInterval;
|
|
150
|
+
constructor(config?: Partial<InterceptorConfig>);
|
|
151
|
+
/**
|
|
152
|
+
* Intercept a tool call — the core entry point.
|
|
153
|
+
* Returns the decision and optionally transformed arguments.
|
|
154
|
+
*/
|
|
155
|
+
intercept(toolName: string, args: Record<string, unknown>): Promise<InterceptedCall>;
|
|
156
|
+
/**
|
|
157
|
+
* Wrap an upstream tool call with interception.
|
|
158
|
+
* If allowed, executes the upstream function; if blocked, returns error.
|
|
159
|
+
*/
|
|
160
|
+
wrapToolCall<T>(toolName: string, args: Record<string, unknown>, upstream: (args: Record<string, unknown>) => Promise<T>): Promise<{
|
|
161
|
+
result?: T;
|
|
162
|
+
intercepted: InterceptedCall;
|
|
163
|
+
error?: string;
|
|
164
|
+
}>;
|
|
165
|
+
/** Get current statistics */
|
|
166
|
+
getStats(): InterceptorStats;
|
|
167
|
+
/** Get recent audit log */
|
|
168
|
+
getAuditLog(limit?: number): AuditEvent[];
|
|
169
|
+
/** Export audit log as CSV */
|
|
170
|
+
exportAuditCSV(): string;
|
|
171
|
+
/** Add a policy at runtime */
|
|
172
|
+
addPolicy(policy: ToolPolicy): void;
|
|
173
|
+
/** Remove policies matching a pattern */
|
|
174
|
+
removePolicies(toolPattern: string): number;
|
|
175
|
+
/** Reset statistics */
|
|
176
|
+
resetStats(): void;
|
|
177
|
+
/** Cleanup resources */
|
|
178
|
+
destroy(): void;
|
|
179
|
+
private emitAudit;
|
|
180
|
+
private trimAuditLog;
|
|
181
|
+
}
|
|
182
|
+
declare const PRESET_POLICIES: {
|
|
183
|
+
/** Block all destructive file operations */
|
|
184
|
+
noDestructiveFiles: {
|
|
185
|
+
toolPattern: string;
|
|
186
|
+
action: "block";
|
|
187
|
+
conditions: {
|
|
188
|
+
field: string;
|
|
189
|
+
operator: "matches";
|
|
190
|
+
value: string;
|
|
191
|
+
}[];
|
|
192
|
+
priority: number;
|
|
193
|
+
description: string;
|
|
194
|
+
};
|
|
195
|
+
/** Block all database write operations */
|
|
196
|
+
readOnlyDatabase: {
|
|
197
|
+
toolPattern: string;
|
|
198
|
+
action: "block";
|
|
199
|
+
conditions: {
|
|
200
|
+
field: string;
|
|
201
|
+
operator: "matches";
|
|
202
|
+
value: string;
|
|
203
|
+
}[];
|
|
204
|
+
priority: number;
|
|
205
|
+
description: string;
|
|
206
|
+
};
|
|
207
|
+
/** Require approval for payment operations */
|
|
208
|
+
approvePayments: {
|
|
209
|
+
toolPattern: string;
|
|
210
|
+
action: "require-approval";
|
|
211
|
+
priority: number;
|
|
212
|
+
description: string;
|
|
213
|
+
};
|
|
214
|
+
/** Redact secrets in tool arguments */
|
|
215
|
+
redactSecrets: {
|
|
216
|
+
toolPattern: string;
|
|
217
|
+
action: "transform";
|
|
218
|
+
conditions: {
|
|
219
|
+
field: string;
|
|
220
|
+
operator: "matches";
|
|
221
|
+
value: string;
|
|
222
|
+
}[];
|
|
223
|
+
transforms: ({
|
|
224
|
+
field: string;
|
|
225
|
+
type: "redact";
|
|
226
|
+
} | {
|
|
227
|
+
field: string;
|
|
228
|
+
type: "mask";
|
|
229
|
+
})[];
|
|
230
|
+
priority: number;
|
|
231
|
+
description: string;
|
|
232
|
+
};
|
|
233
|
+
/** Rate limit shell commands */
|
|
234
|
+
rateLimitShell: {
|
|
235
|
+
toolPattern: string;
|
|
236
|
+
action: "rate-limit";
|
|
237
|
+
priority: number;
|
|
238
|
+
description: string;
|
|
239
|
+
};
|
|
240
|
+
/** Allow read-only operations */
|
|
241
|
+
allowReadOnly: {
|
|
242
|
+
toolPattern: string;
|
|
243
|
+
action: "allow";
|
|
244
|
+
conditions: {
|
|
245
|
+
field: string;
|
|
246
|
+
operator: "matches";
|
|
247
|
+
value: string;
|
|
248
|
+
}[];
|
|
249
|
+
priority: number;
|
|
250
|
+
description: string;
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
/** Create a pre-configured interceptor with common security policies */
|
|
254
|
+
declare function createSecureInterceptor(overrides?: Partial<InterceptorConfig>): McpProxyInterceptor;
|
|
255
|
+
|
|
256
|
+
export { type ApprovalResult, type ArgumentTransform, type AuditEvent, type InterceptDecision, type InterceptedCall, type InterceptorConfig, type InterceptorStats, McpProxyInterceptor, PRESET_POLICIES, type PolicyCondition, type SemanticAnalysisResult, type ToolPolicy, createSecureInterceptor };
|