@virtue-ai/gateway-connect 0.2.0 → 0.2.1
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/README.md +13 -1
- package/dist/index.js +74 -12
- package/dist/trajectory-plugin.js +28 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,11 +28,23 @@ This will:
|
|
|
28
28
|
|
|
29
29
|
### Step 3: Start Using
|
|
30
30
|
|
|
31
|
+
One-shot mode:
|
|
32
|
+
|
|
31
33
|
```bash
|
|
32
34
|
openclaw agent --local --session-id demo --message "What tools do you have?"
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
Interactive TUI (two terminals):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Terminal 1: start the gateway
|
|
41
|
+
openclaw gateway
|
|
42
|
+
|
|
43
|
+
# Terminal 2: open TUI
|
|
44
|
+
openclaw tui
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
OpenClaw now has access to all MCP tools on the gateway (GitHub, Google Workspace, Gmail, Calendar, Slack, PayPal, HR, Firebase, BigQuery, Brave Search, Chrome DevTools, and more).
|
|
36
48
|
|
|
37
49
|
## What It Does
|
|
38
50
|
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ const SCOPES = 'claudeai copilot mcp:read mcp:execute mcp:access';
|
|
|
30
30
|
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
|
|
31
31
|
const MCP_CONFIG_PATH = path.join(OPENCLAW_DIR, 'mcp-gateway.json');
|
|
32
32
|
const OPENCLAW_CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json');
|
|
33
|
+
const MCP_PROXY_PATH = path.join(OPENCLAW_DIR, 'virtueai-mcp-proxy.mjs');
|
|
33
34
|
const DEFAULT_GATEWAY_URL = 'https://virtueai-agent-gtw-l3phon63.ngrok.io';
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
35
36
|
// Helpers
|
|
@@ -223,22 +224,87 @@ async function authenticate(gatewayUrl) {
|
|
|
223
224
|
// ---------------------------------------------------------------------------
|
|
224
225
|
// Step 2: Write MCP gateway config
|
|
225
226
|
// ---------------------------------------------------------------------------
|
|
227
|
+
function generateMcpProxy(gatewayUrl, accessToken) {
|
|
228
|
+
const source = `#!/usr/bin/env node
|
|
229
|
+
// Stdio-to-HTTP MCP proxy for VirtueAI gateway.
|
|
230
|
+
// Claude Code checks OAuth discovery on remote HTTP MCP servers, which
|
|
231
|
+
// conflicts with the gateway's OAuth endpoint. This proxy runs as a local
|
|
232
|
+
// stdio server so Claude Code skips OAuth and uses the pre-obtained token.
|
|
233
|
+
|
|
234
|
+
import { createInterface } from "readline";
|
|
235
|
+
import https from "https";
|
|
236
|
+
import http from "http";
|
|
237
|
+
import { URL } from "url";
|
|
238
|
+
|
|
239
|
+
const GATEWAY_MCP_URL = ${JSON.stringify(gatewayUrl + '/mcp')};
|
|
240
|
+
const TOKEN = ${JSON.stringify(accessToken)};
|
|
241
|
+
|
|
242
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
243
|
+
let buf = "";
|
|
244
|
+
|
|
245
|
+
rl.on("line", (line) => {
|
|
246
|
+
buf += line;
|
|
247
|
+
let msg;
|
|
248
|
+
try { msg = JSON.parse(buf); } catch { return; }
|
|
249
|
+
buf = "";
|
|
250
|
+
forward(msg);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
function forward(msg) {
|
|
254
|
+
const parsed = new URL(GATEWAY_MCP_URL);
|
|
255
|
+
const mod = parsed.protocol === "https:" ? https : http;
|
|
256
|
+
const body = JSON.stringify(msg);
|
|
257
|
+
const req = mod.request(parsed, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: {
|
|
260
|
+
"Content-Type": "application/json",
|
|
261
|
+
"Authorization": "Bearer " + TOKEN,
|
|
262
|
+
"Accept": "application/json, text/event-stream",
|
|
263
|
+
"Content-Length": Buffer.byteLength(body),
|
|
264
|
+
},
|
|
265
|
+
}, (res) => {
|
|
266
|
+
let data = "";
|
|
267
|
+
res.on("data", (c) => data += c);
|
|
268
|
+
res.on("end", () => {
|
|
269
|
+
if (data.trim()) {
|
|
270
|
+
process.stdout.write(data.trim() + "\\n");
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
req.on("error", (e) => {
|
|
275
|
+
if (msg.id != null) {
|
|
276
|
+
process.stdout.write(JSON.stringify({
|
|
277
|
+
jsonrpc: "2.0",
|
|
278
|
+
id: msg.id,
|
|
279
|
+
error: { code: -32000, message: "Proxy error: " + e.message },
|
|
280
|
+
}) + "\\n");
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
req.end(body);
|
|
284
|
+
}
|
|
285
|
+
`;
|
|
286
|
+
fs.writeFileSync(MCP_PROXY_PATH, source, { mode: 0o755 });
|
|
287
|
+
console.log(` Generated proxy: ${MCP_PROXY_PATH}`);
|
|
288
|
+
}
|
|
226
289
|
function writeMcpConfig(gatewayUrl, accessToken, guardUuid) {
|
|
227
290
|
fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
|
|
291
|
+
generateMcpProxy(gatewayUrl, accessToken);
|
|
228
292
|
const config = {
|
|
229
293
|
mcpServers: {
|
|
230
294
|
virtueai: {
|
|
231
|
-
type: '
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Authorization: `Bearer ${accessToken}`,
|
|
235
|
-
},
|
|
295
|
+
type: 'stdio',
|
|
296
|
+
command: 'node',
|
|
297
|
+
args: [MCP_PROXY_PATH],
|
|
236
298
|
},
|
|
237
299
|
},
|
|
238
300
|
trajectory: {
|
|
239
301
|
gatewayUrl,
|
|
240
302
|
guardUuid: guardUuid || process.env.VIRTUEAI_GUARD_UUID || '',
|
|
241
303
|
},
|
|
304
|
+
_auth: {
|
|
305
|
+
gatewayUrl,
|
|
306
|
+
accessToken,
|
|
307
|
+
},
|
|
242
308
|
};
|
|
243
309
|
fs.writeFileSync(MCP_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
244
310
|
console.log(` Written: ${MCP_CONFIG_PATH}`);
|
|
@@ -266,28 +332,24 @@ function patchOpenClawConfig() {
|
|
|
266
332
|
if (!config.agents.defaults.model) {
|
|
267
333
|
config.agents.defaults.model = { primary: 'claude-cli/sonnet' };
|
|
268
334
|
}
|
|
269
|
-
// Ensure cliBackends.claude-cli exists
|
|
335
|
+
// Ensure cliBackends.claude-cli exists (don't override user's existing config)
|
|
270
336
|
if (!config.agents.defaults.cliBackends)
|
|
271
337
|
config.agents.defaults.cliBackends = {};
|
|
272
338
|
if (!config.agents.defaults.cliBackends['claude-cli']) {
|
|
273
339
|
config.agents.defaults.cliBackends['claude-cli'] = {
|
|
274
340
|
command: 'claude',
|
|
275
|
-
args: ['-p', '--output-format', 'json'],
|
|
276
341
|
};
|
|
277
342
|
}
|
|
278
343
|
const cliBackend = config.agents.defaults.cliBackends['claude-cli'];
|
|
279
|
-
//
|
|
344
|
+
// OpenClaw mergeBackendConfig: override.args ?? base.args (full replacement, not merge)
|
|
280
345
|
if (!Array.isArray(cliBackend.args)) {
|
|
281
|
-
cliBackend.args = ['-p', '--output-format', 'json'];
|
|
346
|
+
cliBackend.args = ['-p', '--output-format', 'json', '--permission-mode', 'bypassPermissions'];
|
|
282
347
|
}
|
|
283
|
-
// Add or update --mcp-config
|
|
284
348
|
const mcpIdx = cliBackend.args.indexOf('--mcp-config');
|
|
285
349
|
if (mcpIdx !== -1) {
|
|
286
|
-
// Update existing path
|
|
287
350
|
cliBackend.args[mcpIdx + 1] = MCP_CONFIG_PATH;
|
|
288
351
|
}
|
|
289
352
|
else {
|
|
290
|
-
// Append
|
|
291
353
|
cliBackend.args.push('--mcp-config', MCP_CONFIG_PATH);
|
|
292
354
|
}
|
|
293
355
|
// Write back
|
|
@@ -47,12 +47,9 @@ function loadConfig() {
|
|
|
47
47
|
try {
|
|
48
48
|
const raw = readFileSync(MCP_CONFIG_PATH, "utf-8");
|
|
49
49
|
const cfg = JSON.parse(raw);
|
|
50
|
-
const virtueai = cfg.mcpServers?.virtueai;
|
|
51
|
-
if (!virtueai) return null;
|
|
52
50
|
|
|
53
|
-
const
|
|
54
|
-
const token =
|
|
55
|
-
const gatewayUrl = (virtueai.url ?? "").replace(/\\/mcp\\/?$/, "");
|
|
51
|
+
const gatewayUrl = cfg._auth?.gatewayUrl ?? cfg.trajectory?.gatewayUrl ?? "";
|
|
52
|
+
const token = cfg._auth?.accessToken ?? "";
|
|
56
53
|
|
|
57
54
|
const guardUuid =
|
|
58
55
|
cfg.trajectory?.guardUuid ||
|
|
@@ -86,9 +83,14 @@ const plugin = {
|
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
let gatewaySessionId = null;
|
|
86
|
+
let endpointDisabled = false;
|
|
89
87
|
const endpoint = config.gatewayUrl + "/api/prompt-guard/topic_guard";
|
|
90
88
|
|
|
89
|
+
api.logger.info("[virtueai-trajectory] Plugin registered, sending to " + config.gatewayUrl);
|
|
90
|
+
|
|
91
91
|
async function sendStep(role, content) {
|
|
92
|
+
if (endpointDisabled) return;
|
|
93
|
+
|
|
92
94
|
const body = {
|
|
93
95
|
user_prompt: truncate(content),
|
|
94
96
|
guard_uuid: config.guardUuid,
|
|
@@ -107,8 +109,18 @@ const plugin = {
|
|
|
107
109
|
},
|
|
108
110
|
body: JSON.stringify(body),
|
|
109
111
|
});
|
|
110
|
-
const data = await res.json();
|
|
111
112
|
|
|
113
|
+
if (res.status === 404) {
|
|
114
|
+
endpointDisabled = true;
|
|
115
|
+
api.logger.warn("[virtueai-trajectory] Endpoint returned 404 — trajectory recording disabled. Is the prompt-guard API deployed on the gateway?");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
api.logger.warn("[virtueai-trajectory] HTTP " + res.status + " from gateway");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const data = await res.json();
|
|
112
124
|
if (data?.session_id && !gatewaySessionId) {
|
|
113
125
|
gatewaySessionId = data.session_id;
|
|
114
126
|
api.logger.info("[virtueai-trajectory] Gateway session: " + gatewaySessionId);
|
|
@@ -162,7 +174,7 @@ export function generateTrajectoryPlugin(guardUuid) {
|
|
|
162
174
|
fs.mkdirSync(PLUGIN_DIR, { recursive: true });
|
|
163
175
|
// package.json
|
|
164
176
|
const pkg = {
|
|
165
|
-
name: '@virtue-ai/trajectory',
|
|
177
|
+
name: '@virtue-ai/virtueai-trajectory',
|
|
166
178
|
version: '1.0.0',
|
|
167
179
|
type: 'module',
|
|
168
180
|
openclaw: {
|
|
@@ -170,6 +182,15 @@ export function generateTrajectoryPlugin(guardUuid) {
|
|
|
170
182
|
},
|
|
171
183
|
};
|
|
172
184
|
fs.writeFileSync(path.join(PLUGIN_DIR, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
|
|
185
|
+
// openclaw.plugin.json (manifest required by OpenClaw)
|
|
186
|
+
const manifest = {
|
|
187
|
+
id: PLUGIN_ID,
|
|
188
|
+
name: 'VirtueAI Trajectory',
|
|
189
|
+
description: 'Sends agent trajectory steps to VirtueAI gateway for dashboard visibility',
|
|
190
|
+
version: '1.0.0',
|
|
191
|
+
configSchema: {},
|
|
192
|
+
};
|
|
193
|
+
fs.writeFileSync(path.join(PLUGIN_DIR, 'openclaw.plugin.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
173
194
|
// index.ts (the actual plugin)
|
|
174
195
|
fs.writeFileSync(path.join(PLUGIN_DIR, 'index.ts'), buildPluginSource());
|
|
175
196
|
// Persist guard UUID in mcp-gateway.json trajectory section
|