@intutic/mcp-governance-proxy 1.0.3
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/.turbo/turbo-build.log +1 -0
- package/.turbo/turbo-test.log +51 -0
- package/.turbo/turbo-typecheck.log +1 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +100 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/healthMonitor.d.ts +18 -0
- package/dist/daemon/healthMonitor.d.ts.map +1 -0
- package/dist/daemon/healthMonitor.js +185 -0
- package/dist/daemon/healthMonitor.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +77 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/policyCache.d.ts +20 -0
- package/dist/daemon/policyCache.d.ts.map +1 -0
- package/dist/daemon/policyCache.js +124 -0
- package/dist/daemon/policyCache.js.map +1 -0
- package/dist/daemon/socketServer.d.ts +13 -0
- package/dist/daemon/socketServer.d.ts.map +1 -0
- package/dist/daemon/socketServer.js +90 -0
- package/dist/daemon/socketServer.js.map +1 -0
- package/dist/daemon/telemetryBatcher.d.ts +15 -0
- package/dist/daemon/telemetryBatcher.d.ts.map +1 -0
- package/dist/daemon/telemetryBatcher.js +143 -0
- package/dist/daemon/telemetryBatcher.js.map +1 -0
- package/dist/daemonClient.d.ts +5 -0
- package/dist/daemonClient.d.ts.map +1 -0
- package/dist/daemonClient.js +67 -0
- package/dist/daemonClient.js.map +1 -0
- package/dist/dlp.d.ts +29 -0
- package/dist/dlp.d.ts.map +1 -0
- package/dist/dlp.js +58 -0
- package/dist/dlp.js.map +1 -0
- package/dist/emitter.d.ts +34 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +121 -0
- package/dist/emitter.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptor.d.ts +38 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +93 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/policy.d.ts +41 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +130 -0
- package/dist/policy.js.map +1 -0
- package/dist/proxy.d.ts +43 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +238 -0
- package/dist/proxy.js.map +1 -0
- package/dist/stderrLog.d.ts +32 -0
- package/dist/stderrLog.d.ts.map +1 -0
- package/dist/stderrLog.js +45 -0
- package/dist/stderrLog.js.map +1 -0
- package/dummy-path.jsonl +2 -0
- package/package.json +32 -0
- package/src/__tests__/config.test.ts +82 -0
- package/src/__tests__/dlp.test.ts +103 -0
- package/src/__tests__/integration/daemon-shim.test.ts +125 -0
- package/src/__tests__/interceptor.test.ts +229 -0
- package/src/__tests__/unit/policyCache.test.ts +95 -0
- package/src/__tests__/unit/socketServer.test.ts +220 -0
- package/src/__tests__/unit/telemetryBatcher.test.ts +78 -0
- package/src/config.ts +145 -0
- package/src/daemon/healthMonitor.ts +210 -0
- package/src/daemon/index.ts +76 -0
- package/src/daemon/policyCache.ts +143 -0
- package/src/daemon/socketServer.ts +111 -0
- package/src/daemon/telemetryBatcher.ts +147 -0
- package/src/daemonClient.ts +71 -0
- package/src/dlp.ts +72 -0
- package/src/emitter.ts +144 -0
- package/src/index.ts +53 -0
- package/src/interceptor.ts +103 -0
- package/src/policy.ts +152 -0
- package/src/proxy.ts +327 -0
- package/src/stderrLog.ts +56 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ tsc
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
$ vitest run
|
|
2
|
+
|
|
3
|
+
RUN v3.2.6 /Users/ishangupta/intutic-enterprise/packages/mcp-proxy
|
|
4
|
+
|
|
5
|
+
✓ src/__tests__/dlp.test.ts (14 tests) 9ms
|
|
6
|
+
stderr | src/__tests__/config.test.ts > loadConfig > loads variables from runtime.env and parses CLI arguments
|
|
7
|
+
[mcp-proxy] INTUTIC_MCP_PROXY_MODE=daemon is stored but not yet active — running in per-session mode (Phase 4). Will be activated in Phase 5.
|
|
8
|
+
|
|
9
|
+
✓ src/__tests__/config.test.ts (2 tests) 20ms
|
|
10
|
+
{"level":"info","time":"2026-07-22T18:22:42.382Z","pid":56861,"hostname":"Ishans-MacBook-Pro-9.local","name":"mcp-proxy.policyCache","workspaceId":"ws_test_cache_miss","msg":"policy_cache.invalidated"}
|
|
11
|
+
{"level":"info","time":"2026-07-22T18:22:42.419Z","pid":56861,"hostname":"Ishans-MacBook-Pro-9.local","name":"mcp-proxy.policyCache","workspaceId":"ws_test_cache_miss","msg":"policy_cache.invalidated"}
|
|
12
|
+
✓ src/__tests__/unit/policyCache.test.ts (4 tests) 210ms
|
|
13
|
+
{"level":"debug","time":"2026-07-22T18:22:42.429Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Bash","msg":"Evaluating tool call"}
|
|
14
|
+
{"level":"warn","time":"2026-07-22T18:22:42.429Z","name":"mcp-proxy-interceptor","action":"dlp_block","toolName":"Bash","findings":[{"pattern":"sk-[A-Za-z0-9]{20,}","description":"OpenAI API key pattern"}],"msg":"DLP block"}
|
|
15
|
+
{"level":"debug","time":"2026-07-22T18:22:42.430Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Write","msg":"Evaluating tool call"}
|
|
16
|
+
{"level":"warn","time":"2026-07-22T18:22:42.431Z","name":"mcp-proxy-interceptor","action":"dlp_block","toolName":"Write","findings":[{"pattern":"sk-ant-[A-Za-z0-9\\-_]{20,}","description":"Anthropic API key pattern"}],"msg":"DLP block"}
|
|
17
|
+
{"level":"debug","time":"2026-07-22T18:22:42.431Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Bash","msg":"Evaluating tool call"}
|
|
18
|
+
{"level":"warn","time":"2026-07-22T18:22:42.431Z","name":"mcp-proxy-interceptor","action":"dlp_block","toolName":"Bash","findings":[{"pattern":"rm\\s+-rf?\\s+\\/","description":"Destructive rm -rf / command"}],"msg":"DLP block"}
|
|
19
|
+
{"level":"debug","time":"2026-07-22T18:22:42.431Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"mcp__database__execute","msg":"Evaluating tool call"}
|
|
20
|
+
{"level":"warn","time":"2026-07-22T18:22:42.431Z","name":"mcp-proxy-interceptor","action":"dlp_block","toolName":"mcp__database__execute","findings":[{"pattern":"DROP\\s+TABLE","description":"SQL DROP TABLE statement"}],"msg":"DLP block"}
|
|
21
|
+
{"level":"debug","time":"2026-07-22T18:22:42.431Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Read","msg":"Evaluating tool call"}
|
|
22
|
+
{"level":"debug","time":"2026-07-22T18:22:42.431Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Bash","msg":"Evaluating tool call"}
|
|
23
|
+
{"level":"warn","time":"2026-07-22T18:22:42.432Z","name":"mcp-proxy-interceptor","action":"policy_block","toolName":"Bash","ruleId":"rule-1","reason":"Bash execution not permitted in this workspace","msg":"Policy block"}
|
|
24
|
+
{"level":"debug","time":"2026-07-22T18:22:42.433Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Read","msg":"Evaluating tool call"}
|
|
25
|
+
{"level":"debug","time":"2026-07-22T18:22:42.433Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"mcp__postgres__query","msg":"Evaluating tool call"}
|
|
26
|
+
{"level":"warn","time":"2026-07-22T18:22:42.433Z","name":"mcp-proxy-interceptor","action":"policy_block","toolName":"mcp__postgres__query","ruleId":"rule-2","reason":"Production database access blocked","msg":"Policy block"}
|
|
27
|
+
{"level":"debug","time":"2026-07-22T18:22:42.433Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"mcp__postgres__query","msg":"Evaluating tool call"}
|
|
28
|
+
{"level":"debug","time":"2026-07-22T18:22:42.436Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Write","msg":"Evaluating tool call"}
|
|
29
|
+
{"level":"warn","time":"2026-07-22T18:22:42.438Z","name":"mcp-proxy-interceptor","action":"policy_approval_required","toolName":"Write","ruleId":"rule-3","msg":"Tool requires human approval per SOP rule rule-3: File writes require review"}
|
|
30
|
+
{"level":"debug","time":"2026-07-22T18:22:42.439Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Read","msg":"Evaluating tool call"}
|
|
31
|
+
{"level":"error","time":"2026-07-22T18:22:42.439Z","name":"mcp-proxy-interceptor","action":"policy_error","err":"Policy engine down","msg":"Policy evaluation error"}
|
|
32
|
+
{"level":"debug","time":"2026-07-22T18:22:42.440Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Read","msg":"Evaluating tool call"}
|
|
33
|
+
{"level":"error","time":"2026-07-22T18:22:42.440Z","name":"mcp-proxy-interceptor","action":"policy_error","err":"Policy engine down","msg":"Policy evaluation error"}
|
|
34
|
+
{"level":"debug","time":"2026-07-22T18:22:42.440Z","name":"mcp-proxy-interceptor","action":"interceptor_decide","toolName":"Read","msg":"Evaluating tool call"}
|
|
35
|
+
{"level":"error","time":"2026-07-22T18:22:42.446Z","name":"mcp-proxy-interceptor","action":"dlp_error","err":"Converting circular structure to JSON\n --> starting at object with constructor 'Object'\n --- property 'self' closes the circle","msg":"DLP scan error — skipping"}
|
|
36
|
+
✓ src/__tests__/interceptor.test.ts (13 tests) 19ms
|
|
37
|
+
{"level":"info","time":"2026-07-22T18:22:42.562Z","pid":56863,"hostname":"Ishans-MacBook-Pro-9.local","name":"mcp-proxy.policyCache","workspaceId":"ws_test","msg":"policy_cache.invalidated"}
|
|
38
|
+
{"level":"info","time":"2026-07-22T18:22:42.570Z","name":"mcp-proxy-policy","action":"policy_refreshed_from_daemon","ruleCount":1,"msg":"SOP rules refreshed from daemon"}
|
|
39
|
+
✓ src/__tests__/unit/socketServer.test.ts (5 tests) 144ms
|
|
40
|
+
{"level":"debug","time":"2026-07-22T18:22:42.571Z","name":"mcp-proxy-emitter","action":"telemetry_enqueued","msg":"Telemetry successfully enqueued to daemon"}
|
|
41
|
+
{"level":"warn","time":"2026-07-22T18:22:42.572Z","name":"mcp-proxy-policy","action":"policy_daemon_failed","err":"connect ENOENT /tmp/nonexistent-1784744562571.sock","msg":"Failed to refresh policy from daemon socket — falling back to HTTP"}
|
|
42
|
+
{"level":"debug","time":"2026-07-22T18:22:42.572Z","name":"mcp-proxy-policy","action":"policy_refresh","url":"http://127.0.0.1:51982/api/v1/sop/rules?workspaceId=ws_fallback&active=true","msg":"Fetching SOP rules from control plane"}
|
|
43
|
+
{"level":"info","time":"2026-07-22T18:22:42.573Z","name":"mcp-proxy-policy","action":"policy_refreshed","ruleCount":1,"msg":"SOP rules refreshed"}
|
|
44
|
+
✓ src/__tests__/integration/daemon-shim.test.ts (4 tests) 141ms
|
|
45
|
+
✓ src/__tests__/unit/telemetryBatcher.test.ts (1 test) 446ms
|
|
46
|
+
|
|
47
|
+
Test Files 7 passed (7)
|
|
48
|
+
Tests 43 passed (43)
|
|
49
|
+
Start at 14:22:41
|
|
50
|
+
Duration 1.15s (transform 482ms, setup 0ms, collect 1.25s, tests 989ms, environment 1ms, prepare 1.18s)
|
|
51
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ tsc --noEmit
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Intutic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPORTED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @intutic/mcp-governance-proxy
|
|
2
|
+
|
|
3
|
+
> Transparent stdio proxy that intercepts MCP `tools/call` JSON-RPC messages and applies workspace SOP policy before forwarding to real MCP servers.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The MCP Governance Proxy sits between an AI coding agent and its MCP tool servers. It intercepts every `tools/call` JSON-RPC message on stdin/stdout, evaluates the call against workspace governance policies, and either forwards, blocks, redacts, or flags for approval — all without requiring changes to the agent or the tool server.
|
|
8
|
+
|
|
9
|
+
## Modes
|
|
10
|
+
|
|
11
|
+
### Proxy Mode
|
|
12
|
+
|
|
13
|
+
Intercepts stdio between the agent and an existing MCP server process:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Agent ↔ intutic-mcp-proxy ↔ Real MCP Server (stdio)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The proxy spawns the real MCP server as a child process and relays JSON-RPC messages bidirectionally, applying policy checks on every `tools/call` request.
|
|
20
|
+
|
|
21
|
+
### Standalone Mode
|
|
22
|
+
|
|
23
|
+
Runs as its own MCP server that exposes governance-aware tool wrappers:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Agent ↔ intutic-mcp-daemon (MCP server)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
### DLP Scanning
|
|
32
|
+
|
|
33
|
+
14 regex patterns detect and block sensitive data in tool arguments and responses:
|
|
34
|
+
|
|
35
|
+
- API keys and tokens (AWS, GCP, GitHub, Stripe, etc.)
|
|
36
|
+
- Secrets and passwords
|
|
37
|
+
- Private keys and certificates
|
|
38
|
+
- SQL injection patterns
|
|
39
|
+
- PII indicators
|
|
40
|
+
|
|
41
|
+
### Policy Enforcement
|
|
42
|
+
|
|
43
|
+
- **SOP rule matching**: Maps tool names and argument patterns to workspace SOP rules
|
|
44
|
+
- **Enforcement actions**: `allow`, `block`, `redact`, `require_approval`
|
|
45
|
+
- **Audit logging**: Every intercepted call is logged with policy evaluation results
|
|
46
|
+
|
|
47
|
+
## Binaries
|
|
48
|
+
|
|
49
|
+
| Binary | Description |
|
|
50
|
+
|--------|-------------|
|
|
51
|
+
| `intutic-mcp-proxy` | Stdio proxy — wraps an existing MCP server |
|
|
52
|
+
| `intutic-mcp-daemon` | Standalone MCP server with governance built in |
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @intutic/mcp-governance-proxy
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### Proxy Mode
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"filesystem": {
|
|
68
|
+
"command": "intutic-mcp-proxy",
|
|
69
|
+
"args": ["--", "npx", "@modelcontextprotocol/server-filesystem", "/path"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Standalone Mode
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
intutic-mcp-daemon --port 3100
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Part of Intutic
|
|
82
|
+
|
|
83
|
+
This package is part of the [Intutic](https://github.com/intutic/intutic) monorepo — an open-core AI governance control plane for developer teams.
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT — see [LICENSE](../../LICENSE) for details.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config.ts — MCP Governance Proxy configuration.
|
|
3
|
+
*
|
|
4
|
+
* Reads from ~/.intutic/env/runtime.env (written by sync-daemon runtimeEnv.ts)
|
|
5
|
+
* and from CLI arguments passed after `--`.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export interface ProxyConfig {
|
|
10
|
+
/** Workspace ID for policy lookups and event attribution */
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
/** Control plane base URL (e.g. http://localhost:3001) */
|
|
13
|
+
controlPlaneUrl: string;
|
|
14
|
+
/** API key for control plane requests */
|
|
15
|
+
apiKey: string;
|
|
16
|
+
/** Real MCP server command + args (everything after --). Empty = standalone mode. */
|
|
17
|
+
realServerCommand: string[];
|
|
18
|
+
/** Path to write hook-events JSONL (Path B) */
|
|
19
|
+
eventsFilePath: string;
|
|
20
|
+
/** Policy cache TTL in milliseconds (default: 60_000) */
|
|
21
|
+
policyTtlMs: number;
|
|
22
|
+
/** Whether to fail-open when control plane is unreachable (default: true) */
|
|
23
|
+
failOpen: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* MCP proxy deployment model ('per-session' | 'daemon').
|
|
26
|
+
* WS-5 Q2 — stored and propagated but 'daemon' is not yet active in Phase 4.
|
|
27
|
+
*/
|
|
28
|
+
mcpProxyMode: string;
|
|
29
|
+
/**
|
|
30
|
+
* Standalone mode — when true, the proxy acts as the Intutic MCP server
|
|
31
|
+
* directly (no downstream server to proxy). Used for the `intutic` harness
|
|
32
|
+
* entry that exposes Intutic governance tools to the harness.
|
|
33
|
+
*/
|
|
34
|
+
standalone: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Load the proxy configuration from runtime.env + CLI args + environment.
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadConfig(argv?: string[]): Promise<ProxyConfig>;
|
|
40
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAA;IACvB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,qFAAqF;IACrF,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAA;IACtB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;IACnB,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAA;CACpB;AAqDD;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,WAAW,CAAC,CAiD7F"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config.ts — MCP Governance Proxy configuration.
|
|
3
|
+
*
|
|
4
|
+
* Reads from ~/.intutic/env/runtime.env (written by sync-daemon runtimeEnv.ts)
|
|
5
|
+
* and from CLI arguments passed after `--`.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import * as node_fs from 'node:fs/promises';
|
|
10
|
+
import * as node_os from 'node:os';
|
|
11
|
+
import * as node_path from 'node:path';
|
|
12
|
+
const DEFAULT_EVENTS_PATH = node_path.join(node_os.homedir(), '.intutic', 'events', 'hook-events.jsonl');
|
|
13
|
+
const RUNTIME_ENV_PATH = node_path.join(node_os.homedir(), '.intutic', 'env', 'runtime.env');
|
|
14
|
+
/**
|
|
15
|
+
* Parse a .env-format file into a key→value record.
|
|
16
|
+
* Lines starting with # are comments; blank lines are skipped.
|
|
17
|
+
*/
|
|
18
|
+
async function parseEnvFile(filePath) {
|
|
19
|
+
const result = {};
|
|
20
|
+
try {
|
|
21
|
+
const content = await node_fs.readFile(filePath, 'utf-8');
|
|
22
|
+
for (const line of content.split('\n')) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
25
|
+
continue;
|
|
26
|
+
const eq = trimmed.indexOf('=');
|
|
27
|
+
if (eq === -1)
|
|
28
|
+
continue;
|
|
29
|
+
const key = trimmed.slice(0, eq).trim();
|
|
30
|
+
// Strip surrounding quotes from value
|
|
31
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
32
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
33
|
+
value = value.slice(1, -1);
|
|
34
|
+
}
|
|
35
|
+
result[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// File may not exist — return empty record
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse CLI arguments for --workspace-id and the real server command (after --).
|
|
45
|
+
*/
|
|
46
|
+
function parseCliArgs(argv) {
|
|
47
|
+
let workspaceId;
|
|
48
|
+
let separatorIndex = -1;
|
|
49
|
+
for (let i = 0; i < argv.length; i++) {
|
|
50
|
+
if (argv[i] === '--workspace-id' && i + 1 < argv.length) {
|
|
51
|
+
workspaceId = argv[i + 1];
|
|
52
|
+
i++; // skip value
|
|
53
|
+
}
|
|
54
|
+
else if (argv[i] === '--') {
|
|
55
|
+
separatorIndex = i;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const realServerCommand = separatorIndex !== -1 ? argv.slice(separatorIndex + 1) : [];
|
|
60
|
+
return { workspaceId, realServerCommand };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load the proxy configuration from runtime.env + CLI args + environment.
|
|
64
|
+
*/
|
|
65
|
+
export async function loadConfig(argv = process.argv.slice(2)) {
|
|
66
|
+
const runtimeEnv = await parseEnvFile(RUNTIME_ENV_PATH);
|
|
67
|
+
const cli = parseCliArgs(argv);
|
|
68
|
+
const controlPlaneUrl = process.env['INTUTIC_CONTROL_PLANE_URL'] ??
|
|
69
|
+
runtimeEnv['INTUTIC_CONTROL_PLANE_URL'] ??
|
|
70
|
+
'http://localhost:3001';
|
|
71
|
+
const apiKey = process.env['INTUTIC_API_KEY'] ??
|
|
72
|
+
runtimeEnv['INTUTIC_API_KEY'] ??
|
|
73
|
+
'';
|
|
74
|
+
const workspaceId = cli.workspaceId ??
|
|
75
|
+
process.env['INTUTIC_WORKSPACE_ID'] ??
|
|
76
|
+
runtimeEnv['INTUTIC_WORKSPACE_ID'] ??
|
|
77
|
+
'unknown';
|
|
78
|
+
const eventsFilePath = process.env['INTUTIC_EVENTS_FILE'] ??
|
|
79
|
+
runtimeEnv['INTUTIC_EVENTS_FILE'] ??
|
|
80
|
+
DEFAULT_EVENTS_PATH;
|
|
81
|
+
const failOpen = (runtimeEnv['INTUTIC_MCP_FAIL_OPEN'] ?? 'true').toLowerCase() !== 'false';
|
|
82
|
+
const mcpProxyMode = runtimeEnv['INTUTIC_MCP_PROXY_MODE'] ?? 'per-session';
|
|
83
|
+
if (mcpProxyMode === 'daemon') {
|
|
84
|
+
console.warn('[mcp-proxy] INTUTIC_MCP_PROXY_MODE=daemon is stored but not yet active — '
|
|
85
|
+
+ 'running in per-session mode (Phase 4). Will be activated in Phase 5.');
|
|
86
|
+
}
|
|
87
|
+
const standalone = cli.realServerCommand.length === 0;
|
|
88
|
+
return {
|
|
89
|
+
workspaceId,
|
|
90
|
+
controlPlaneUrl,
|
|
91
|
+
apiKey,
|
|
92
|
+
realServerCommand: cli.realServerCommand,
|
|
93
|
+
eventsFilePath,
|
|
94
|
+
policyTtlMs: 60_000,
|
|
95
|
+
failOpen,
|
|
96
|
+
mcpProxyMode,
|
|
97
|
+
standalone,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface McpServerConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
credentialExpiryAt?: Date;
|
|
5
|
+
}
|
|
6
|
+
export interface McpServerHealth {
|
|
7
|
+
serverName: string;
|
|
8
|
+
status: 'healthy' | 'degraded' | 'unreachable';
|
|
9
|
+
p95LatencyMs?: number;
|
|
10
|
+
errorRatePct?: number;
|
|
11
|
+
credentialExpiryAt?: string;
|
|
12
|
+
checkedAt: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function registerServer(server: McpServerConfig): void;
|
|
15
|
+
export declare function startHealthMonitor(): void;
|
|
16
|
+
export declare function stopHealthMonitor(): void;
|
|
17
|
+
export declare function getHealthSnapshot(): McpServerHealth[];
|
|
18
|
+
//# sourceMappingURL=healthMonitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"healthMonitor.d.ts","sourceRoot":"","sources":["../../src/daemon/healthMonitor.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAM,MAAM,CAAA;IAChB,GAAG,EAAO,MAAM,CAAA;IAChB,kBAAkB,CAAC,EAAE,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAW,MAAM,CAAA;IAC3B,MAAM,EAAe,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;IAC3D,YAAY,CAAC,EAAQ,MAAM,CAAA;IAC3B,YAAY,CAAC,EAAQ,MAAM,CAAA;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,SAAS,EAAY,MAAM,CAAA;CAC5B;AAuID,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAE5D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAuBzC;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,wBAAgB,iBAAiB,IAAI,eAAe,EAAE,CAErD"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Health Monitor
|
|
3
|
+
*
|
|
4
|
+
* Probes MCP servers every 30s and records health snapshots.
|
|
5
|
+
* Emits mcp_daemon.mcp_server_down when a server becomes unreachable.
|
|
6
|
+
*
|
|
7
|
+
* LLD #28: MCP Daemon Mode, WS-5MCP
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import https from 'node:https';
|
|
11
|
+
import http from 'node:http';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import { createLogger } from '@intutic/logger';
|
|
17
|
+
const logger = createLogger('mcp-proxy.healthMonitor');
|
|
18
|
+
const HEARTBEAT_MS = 30_000;
|
|
19
|
+
const PROBE_TIMEOUT = 5_000;
|
|
20
|
+
const CP_URL = process.env['CONTROL_PLANE_URL'] ?? 'http://localhost:3001';
|
|
21
|
+
const DAEMON_API_KEY = process.env['INTUTIC_API_KEY'] ?? '';
|
|
22
|
+
const WORKSPACE_ID = process.env['INTUTIC_WORKSPACE_ID'] ?? '';
|
|
23
|
+
const servers = [];
|
|
24
|
+
const latestHealth = new Map();
|
|
25
|
+
let timer = null;
|
|
26
|
+
function discoverServers() {
|
|
27
|
+
const discovered = [];
|
|
28
|
+
const homedir = os.homedir();
|
|
29
|
+
const configPaths = [
|
|
30
|
+
path.join(homedir, '.claude', 'mcp.json'),
|
|
31
|
+
process.platform === 'darwin'
|
|
32
|
+
? path.join(homedir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
|
|
33
|
+
: process.platform === 'win32'
|
|
34
|
+
? path.join(process.env['APPDATA'] ?? '', 'Claude', 'claude_desktop_config.json')
|
|
35
|
+
: path.join(homedir, '.config', 'Claude', 'claude_desktop_config.json'),
|
|
36
|
+
process.platform === 'darwin'
|
|
37
|
+
? path.join(homedir, 'Library', 'Application Support', 'Cursor', 'User', 'globalSettings.json')
|
|
38
|
+
: process.platform === 'win32'
|
|
39
|
+
? path.join(process.env['APPDATA'] ?? '', 'Cursor', 'User', 'globalSettings.json')
|
|
40
|
+
: path.join(homedir, '.config', 'Cursor', 'User', 'globalSettings.json'),
|
|
41
|
+
];
|
|
42
|
+
for (const configPath of configPaths) {
|
|
43
|
+
try {
|
|
44
|
+
if (!fs.existsSync(configPath))
|
|
45
|
+
continue;
|
|
46
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
const mcpServers = parsed.mcpServers ?? parsed.mcp ?? {};
|
|
49
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
50
|
+
if (name === 'intutic')
|
|
51
|
+
continue; // Skip self
|
|
52
|
+
const serverEntry = entry;
|
|
53
|
+
let url = '';
|
|
54
|
+
if (serverEntry.url) {
|
|
55
|
+
url = serverEntry.url;
|
|
56
|
+
}
|
|
57
|
+
else if (serverEntry.command) {
|
|
58
|
+
url = `stdio://${serverEntry.command}`;
|
|
59
|
+
}
|
|
60
|
+
if (url && !discovered.some(s => s.name === name)) {
|
|
61
|
+
discovered.push({
|
|
62
|
+
name,
|
|
63
|
+
url,
|
|
64
|
+
credentialExpiryAt: undefined
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
}
|
|
71
|
+
return discovered;
|
|
72
|
+
}
|
|
73
|
+
async function probeServer(server) {
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
if (server.url.startsWith('stdio://')) {
|
|
76
|
+
const cmd = server.url.replace('stdio://', '');
|
|
77
|
+
let status = 'healthy';
|
|
78
|
+
try {
|
|
79
|
+
const whichCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
|
|
80
|
+
execSync(whichCmd, { stdio: 'ignore' });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
status = 'unreachable';
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
serverName: server.name,
|
|
87
|
+
status,
|
|
88
|
+
p95LatencyMs: Date.now() - start,
|
|
89
|
+
credentialExpiryAt: server.credentialExpiryAt?.toISOString(),
|
|
90
|
+
checkedAt: new Date().toISOString()
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
try {
|
|
95
|
+
const url = new URL(server.url);
|
|
96
|
+
const isHttps = url.protocol === 'https:';
|
|
97
|
+
const lib = isHttps ? https : http;
|
|
98
|
+
const req = lib.request({ hostname: url.hostname, port: url.port, path: url.pathname + url.search, method: 'GET' }, (res) => {
|
|
99
|
+
res.resume();
|
|
100
|
+
const latency = Date.now() - start;
|
|
101
|
+
const status = res.statusCode && res.statusCode < 500 ? 'healthy' : 'degraded';
|
|
102
|
+
resolve({ serverName: server.name, status, p95LatencyMs: latency,
|
|
103
|
+
credentialExpiryAt: server.credentialExpiryAt?.toISOString(),
|
|
104
|
+
checkedAt: new Date().toISOString() });
|
|
105
|
+
});
|
|
106
|
+
req.on('error', () => resolve({ serverName: server.name, status: 'unreachable',
|
|
107
|
+
checkedAt: new Date().toISOString() }));
|
|
108
|
+
req.setTimeout(PROBE_TIMEOUT, () => {
|
|
109
|
+
req.destroy();
|
|
110
|
+
resolve({
|
|
111
|
+
serverName: server.name, status: 'unreachable', checkedAt: new Date().toISOString()
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
req.end();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
resolve({ serverName: server.name, status: 'unreachable', checkedAt: new Date().toISOString() });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function uploadSnapshots(snapshots) {
|
|
122
|
+
if (snapshots.length === 0 || !WORKSPACE_ID)
|
|
123
|
+
return;
|
|
124
|
+
const body = JSON.stringify({ mcpServers: snapshots });
|
|
125
|
+
const url = new URL('/api/v1/mcp-daemon/health-snapshot', CP_URL);
|
|
126
|
+
const isHttps = url.protocol === 'https:';
|
|
127
|
+
const lib = isHttps ? https : http;
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
const req = lib.request({
|
|
130
|
+
hostname: url.hostname,
|
|
131
|
+
port: url.port,
|
|
132
|
+
path: url.pathname,
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
'Content-Length': Buffer.byteLength(body),
|
|
137
|
+
'Authorization': `Bearer ${DAEMON_API_KEY}`,
|
|
138
|
+
'x-workspace-id': WORKSPACE_ID
|
|
139
|
+
}
|
|
140
|
+
}, (res) => {
|
|
141
|
+
res.resume();
|
|
142
|
+
resolve();
|
|
143
|
+
});
|
|
144
|
+
req.on('error', () => resolve());
|
|
145
|
+
req.setTimeout(5000, () => { req.destroy(); resolve(); });
|
|
146
|
+
req.write(body);
|
|
147
|
+
req.end();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
export function registerServer(server) {
|
|
151
|
+
servers.push(server);
|
|
152
|
+
}
|
|
153
|
+
export function startHealthMonitor() {
|
|
154
|
+
// Run config discovery
|
|
155
|
+
const discovered = discoverServers();
|
|
156
|
+
for (const s of discovered) {
|
|
157
|
+
registerServer(s);
|
|
158
|
+
}
|
|
159
|
+
timer = setInterval(async () => {
|
|
160
|
+
const snapshots = [];
|
|
161
|
+
for (const server of servers) {
|
|
162
|
+
const health = await probeServer(server);
|
|
163
|
+
const prev = latestHealth.get(server.name);
|
|
164
|
+
latestHealth.set(server.name, health);
|
|
165
|
+
snapshots.push(health);
|
|
166
|
+
if (health.status === 'unreachable' && prev?.status !== 'unreachable') {
|
|
167
|
+
logger.warn({ serverName: server.name }, 'mcp_daemon.mcp_server_down');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (snapshots.length > 0) {
|
|
171
|
+
await uploadSnapshots(snapshots);
|
|
172
|
+
}
|
|
173
|
+
}, HEARTBEAT_MS);
|
|
174
|
+
timer.unref();
|
|
175
|
+
}
|
|
176
|
+
export function stopHealthMonitor() {
|
|
177
|
+
if (timer) {
|
|
178
|
+
clearInterval(timer);
|
|
179
|
+
timer = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function getHealthSnapshot() {
|
|
183
|
+
return Array.from(latestHealth.values());
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=healthMonitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"healthMonitor.js","sourceRoot":"","sources":["../../src/daemon/healthMonitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* intutic-mcp-daemon entry point
|
|
3
|
+
*
|
|
4
|
+
* Long-lived process listening on ~/.intutic/mcp-proxy.sock.
|
|
5
|
+
* Manages policy cache, telemetry batching, and MCP server health monitoring.
|
|
6
|
+
*
|
|
7
|
+
* LLD #28: MCP Daemon Mode, WS-5MCP
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node dist/daemon/index.js
|
|
11
|
+
* (managed by LaunchAgent / systemd via `intutic daemon mcp start`)
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import { createLogger } from '@intutic/logger';
|
|
19
|
+
import { startBatcher, stopBatcher } from './telemetryBatcher.js';
|
|
20
|
+
import { startHealthMonitor, stopHealthMonitor } from './healthMonitor.js';
|
|
21
|
+
import { createSocketServer, getSocketPath } from './socketServer.js';
|
|
22
|
+
const logger = createLogger('intutic-mcp-daemon');
|
|
23
|
+
const PID_FILE = path.join(os.homedir(), '.intutic', 'mcp-daemon.pid');
|
|
24
|
+
function ensureDir(dir) {
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
26
|
+
}
|
|
27
|
+
async function main() {
|
|
28
|
+
const socketPath = getSocketPath();
|
|
29
|
+
logger.info({ pid: process.pid, socketPath }, 'mcp_daemon.starting');
|
|
30
|
+
// Ensure ~/.intutic directory exists with correct permissions
|
|
31
|
+
ensureDir(path.dirname(socketPath));
|
|
32
|
+
// Remove stale socket file
|
|
33
|
+
try {
|
|
34
|
+
fs.unlinkSync(socketPath);
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
// Write PID file
|
|
38
|
+
fs.writeFileSync(PID_FILE, String(process.pid), { mode: 0o600 });
|
|
39
|
+
// Start subsystems
|
|
40
|
+
startBatcher();
|
|
41
|
+
startHealthMonitor();
|
|
42
|
+
const server = createSocketServer();
|
|
43
|
+
server.listen(socketPath, () => {
|
|
44
|
+
// Restrict socket to owner only
|
|
45
|
+
try {
|
|
46
|
+
fs.chmodSync(socketPath, 0o600);
|
|
47
|
+
}
|
|
48
|
+
catch { }
|
|
49
|
+
logger.info({ socketPath, pid: process.pid }, 'mcp_daemon.running');
|
|
50
|
+
process.stdout.write(JSON.stringify({ event: 'mcp_daemon.started', pid: process.pid }) + '\n');
|
|
51
|
+
});
|
|
52
|
+
server.on('error', (err) => {
|
|
53
|
+
logger.error({ err }, 'mcp_daemon.socket_error');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|
|
56
|
+
// Graceful shutdown
|
|
57
|
+
const shutdown = async (signal) => {
|
|
58
|
+
logger.info({ signal }, 'mcp_daemon.stopping');
|
|
59
|
+
server.close();
|
|
60
|
+
stopHealthMonitor();
|
|
61
|
+
await stopBatcher();
|
|
62
|
+
try {
|
|
63
|
+
fs.unlinkSync(socketPath);
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
try {
|
|
67
|
+
fs.unlinkSync(PID_FILE);
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
logger.info('mcp_daemon.stopped');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
};
|
|
73
|
+
process.on('SIGTERM', () => { void shutdown('SIGTERM'); });
|
|
74
|
+
process.on('SIGINT', () => { void shutdown('SIGINT'); });
|
|
75
|
+
}
|
|
76
|
+
void main();
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":"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"}
|