@virtue-ai/gateway-connect 0.2.1 → 0.3.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @virtue-ai/gateway-connect
2
2
 
3
- One-command setup to connect [OpenClaw](https://github.com/openclaw/openclaw) to the VirtueAI MCP gateway.
3
+ One-command setup to connect [OpenClaw](https://github.com/openclaw/openclaw) to the VirtueAI MCP gateway. Works with **any model** — Anthropic, OpenAI, Google, LiteLLM, etc.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -10,51 +10,86 @@ One-command setup to connect [OpenClaw](https://github.com/openclaw/openclaw) to
10
10
  npm install -g openclaw@latest
11
11
  ```
12
12
 
13
- Make sure you have [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and logged in (`claude` command should work).
13
+ ### Step 2: Configure Model Auth
14
14
 
15
- ### Step 2: Connect to VirtueAI MCP Gateway
15
+ Set up API key for your preferred model provider:
16
+
17
+ ```bash
18
+ openclaw models auth paste-token --provider anthropic
19
+ # or
20
+ openclaw models auth paste-token --provider openai
21
+ ```
22
+
23
+ ### Step 3: Connect to VirtueAI MCP Gateway
16
24
 
17
25
  ```bash
18
26
  npx @virtue-ai/gateway-connect --gateway-url https://virtueai-agent-gtw-xxxx.ngrok.io
19
27
  ```
20
28
 
29
+ To use a specific model:
30
+
31
+ ```bash
32
+ npx @virtue-ai/gateway-connect --gateway-url https://virtueai-agent-gtw-xxxx.ngrok.io --model openai/gpt-4o
33
+ ```
34
+
21
35
  This will:
22
36
 
23
- 1. Open your browser for OAuth login (select "Authorize the platform")
24
- 2. Save gateway credentials to `~/.openclaw/mcp-gateway.json`
25
- 3. Patch `~/.openclaw/openclaw.json` to connect claude-cli to the gateway
26
- 4. Install the trajectory recording plugin (sends full session trace to VirtueAI dashboard)
27
- 5. Verify connection and list available tools
37
+ 1. Open your browser for OAuth login
38
+ 2. Fetch all MCP tools from the gateway
39
+ 3. Generate a native OpenClaw plugin wrapping each tool (`~/.openclaw/extensions/virtueai-mcp-tools/`)
40
+ 4. Patch `~/.openclaw/openclaw.json` to enable the plugin
41
+ 5. Install the trajectory recording plugin
28
42
 
29
- ### Step 3: Start Using
43
+ ### Step 4: Start Using
30
44
 
31
45
  One-shot mode:
32
46
 
33
47
  ```bash
34
- openclaw agent --local --session-id demo --message "What tools do you have?"
48
+ openclaw agent --local --message "What tools do you have?"
35
49
  ```
36
50
 
37
- Interactive TUI (two terminals):
51
+ Interactive TUI mode:
38
52
 
39
53
  ```bash
40
- # Terminal 1: start the gateway
54
+ # Terminal 1: start the OpenClaw gateway
41
55
  openclaw gateway
42
56
 
43
- # Terminal 2: open TUI
57
+ # Terminal 2: open the TUI
44
58
  openclaw tui
45
59
  ```
46
60
 
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).
61
+ To stop the gateway, press `Ctrl+C` in Terminal 1, or run:
62
+
63
+ ```bash
64
+ openclaw gateway stop
65
+ ```
66
+
67
+ ### Switching Models
68
+
69
+ In TUI mode, use slash commands to switch models on the fly:
70
+
71
+ ```
72
+ /model openai/gpt-4o
73
+ /model anthropic/claude-opus-4-6
74
+ /models # opens model picker
75
+ ```
76
+
77
+ All gateway tools remain available regardless of which model you use. Any model with a configured API key can be selected.
48
78
 
49
79
  ## What It Does
50
80
 
51
- `gateway-connect` automates the following:
81
+ `gateway-connect` generates a **native OpenClaw plugin** that registers every MCP gateway tool via `api.registerTool()`. Each tool call is proxied to the gateway as a JSON-RPC `tools/call` request with Bearer auth.
82
+
83
+ This approach works with any embedded model provider (not just Claude CLI), because the tools are part of OpenClaw's plugin system rather than being passed via `--mcp-config`.
84
+
85
+ ### Generated Files
52
86
 
53
- 1. **OAuth 2.0 PKCE authentication** — Registers an OAuth client, opens browser for login, exchanges authorization code for tokens
54
- 2. **MCP config generation** — Writes `~/.openclaw/mcp-gateway.json` with gateway URL and bearer token
55
- 3. **OpenClaw config patching** — Adds `--mcp-config` to the claude-cli backend args in `~/.openclaw/openclaw.json`
56
- 4. **Trajectory recording** — Installs an OpenClaw plugin (`virtueai-trajectory`) that automatically sends every agent step (user prompts, agent responses, tool calls) to the VirtueAI prompt-guard API for dashboard visibility
57
- 5. **Connection verification** — Calls `tools/list` on the gateway and reports available tools
87
+ | Path | Purpose |
88
+ |------|---------|
89
+ | `~/.openclaw/extensions/virtueai-mcp-tools/` | Native plugin with all gateway tools |
90
+ | `~/.openclaw/extensions/virtueai-trajectory/` | Trajectory recording plugin |
91
+ | `~/.openclaw/mcp-gateway.json` | Auth & trajectory config |
92
+ | `~/.openclaw/openclaw.json` | Patched with plugin entries |
58
93
 
59
94
  ## Options
60
95
 
@@ -62,14 +97,15 @@ OpenClaw now has access to all MCP tools on the gateway (GitHub, Google Workspac
62
97
  npx @virtue-ai/gateway-connect [options]
63
98
 
64
99
  Options:
65
- --gateway-url <url> Gateway URL (required)
66
- --guard-uuid <uuid> Guard UUID for trajectory recording (or set VIRTUEAI_GUARD_UUID env var)
100
+ --gateway-url <url> Gateway URL (default: https://virtueai-agent-gtw-l3phon63.ngrok.io)
101
+ --model <model> Model to use (e.g. openai/gpt-4o, anthropic/claude-sonnet-4-5)
102
+ --guard-uuid <uuid> Guard UUID for trajectory recording (or set VIRTUEAI_GUARD_UUID)
67
103
  --help Show help message
68
104
  ```
69
105
 
70
106
  ## Re-authentication
71
107
 
72
- If your token expires, just run the command again. It will update the existing config files.
108
+ If your token expires, just run the command again. It will regenerate the plugin with a fresh token.
73
109
 
74
110
  ## License
75
111
 
package/dist/index.d.ts CHANGED
@@ -5,9 +5,10 @@
5
5
  * One-command setup to connect OpenClaw to VirtueAI MCP gateway.
6
6
  *
7
7
  * 1. OAuth 2.0 PKCE login → obtain access token
8
- * 2. Write MCP gateway config to ~/.openclaw/mcp-gateway.json
9
- * 3. Patch ~/.openclaw/openclaw.json to use claude-cli with --mcp-config
10
- * 4. Verify connection by listing tools
8
+ * 2. Fetch all MCP tools from the gateway
9
+ * 3. Generate a native OpenClaw plugin that wraps each tool (works with any model)
10
+ * 4. Patch ~/.openclaw/openclaw.json to enable the plugin
11
+ * 5. Install trajectory recording plugin
11
12
  *
12
13
  * Usage: npx @virtue-ai/gateway-connect [--gateway-url https://...]
13
14
  */
package/dist/index.js CHANGED
@@ -5,9 +5,10 @@
5
5
  * One-command setup to connect OpenClaw to VirtueAI MCP gateway.
6
6
  *
7
7
  * 1. OAuth 2.0 PKCE login → obtain access token
8
- * 2. Write MCP gateway config to ~/.openclaw/mcp-gateway.json
9
- * 3. Patch ~/.openclaw/openclaw.json to use claude-cli with --mcp-config
10
- * 4. Verify connection by listing tools
8
+ * 2. Fetch all MCP tools from the gateway
9
+ * 3. Generate a native OpenClaw plugin that wraps each tool (works with any model)
10
+ * 4. Patch ~/.openclaw/openclaw.json to enable the plugin
11
+ * 5. Install trajectory recording plugin
11
12
  *
12
13
  * Usage: npx @virtue-ai/gateway-connect [--gateway-url https://...]
13
14
  */
@@ -30,7 +31,8 @@ const SCOPES = 'claudeai copilot mcp:read mcp:execute mcp:access';
30
31
  const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
31
32
  const MCP_CONFIG_PATH = path.join(OPENCLAW_DIR, 'mcp-gateway.json');
32
33
  const OPENCLAW_CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json');
33
- const MCP_PROXY_PATH = path.join(OPENCLAW_DIR, 'virtueai-mcp-proxy.mjs');
34
+ const TOOLS_PLUGIN_ID = 'virtueai-mcp-tools';
35
+ const TOOLS_PLUGIN_DIR = path.join(OPENCLAW_DIR, 'extensions', TOOLS_PLUGIN_ID);
34
36
  const DEFAULT_GATEWAY_URL = 'https://virtueai-agent-gtw-l3phon63.ngrok.io';
35
37
  // ---------------------------------------------------------------------------
36
38
  // Helpers
@@ -97,7 +99,6 @@ function openBrowser(url) {
97
99
  // Step 1: OAuth PKCE Authentication
98
100
  // ---------------------------------------------------------------------------
99
101
  async function authenticate(gatewayUrl) {
100
- // Discover OAuth metadata
101
102
  console.log(' Discovering OAuth endpoints...');
102
103
  const { data: metadata } = await fetchJson(`${gatewayUrl}/.well-known/oauth-authorization-server`, { method: 'GET' });
103
104
  if (!metadata.authorization_endpoint || !metadata.token_endpoint) {
@@ -110,7 +111,6 @@ async function authenticate(gatewayUrl) {
110
111
  const registerEndpoint = metadata.registration_endpoint;
111
112
  console.log(` Auth endpoint: ${authEndpoint}`);
112
113
  console.log(` Token endpoint: ${tokenEndpoint}`);
113
- // Register OAuth client
114
114
  console.log(' Registering OAuth client...');
115
115
  const { status: regStatus, data: clientInfo } = await fetchJson(registerEndpoint, {
116
116
  method: 'POST',
@@ -130,7 +130,6 @@ async function authenticate(gatewayUrl) {
130
130
  }
131
131
  const clientId = clientInfo.client_id;
132
132
  console.log(` Client ID: ${clientId}`);
133
- // Build authorization URL with PKCE
134
133
  const codeVerifier = generateCodeVerifier();
135
134
  const codeChallenge = generateCodeChallenge(codeVerifier);
136
135
  const state = crypto.randomBytes(16).toString('hex');
@@ -142,7 +141,6 @@ async function authenticate(gatewayUrl) {
142
141
  authUrl.searchParams.set('state', state);
143
142
  authUrl.searchParams.set('code_challenge', codeChallenge);
144
143
  authUrl.searchParams.set('code_challenge_method', 'S256');
145
- // Start callback server & open browser
146
144
  const authCode = await new Promise((resolve, reject) => {
147
145
  const server = http.createServer((req, res) => {
148
146
  if (!req.url?.startsWith(CALLBACK_PATH)) {
@@ -190,14 +188,12 @@ async function authenticate(gatewayUrl) {
190
188
  reject(err);
191
189
  }
192
190
  });
193
- // Timeout after 5 minutes
194
191
  setTimeout(() => {
195
192
  server.close();
196
193
  reject(new Error('Authentication timed out (5 minutes). Please try again.'));
197
194
  }, 5 * 60 * 1000);
198
195
  });
199
196
  console.log(' Authorization code received. Exchanging for tokens...');
200
- // Exchange code for tokens
201
197
  const tokenBody = new URLSearchParams({
202
198
  grant_type: 'authorization_code',
203
199
  client_id: clientId,
@@ -221,82 +217,132 @@ async function authenticate(gatewayUrl) {
221
217
  clientId,
222
218
  };
223
219
  }
224
- // ---------------------------------------------------------------------------
225
- // Step 2: Write MCP gateway config
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";
220
+ async function fetchToolsList(gatewayUrl, accessToken) {
221
+ console.log(' Fetching tools from gateway...');
222
+ const { status, data } = await fetchJson(`${gatewayUrl}/mcp`, {
223
+ method: 'POST',
224
+ headers: {
225
+ 'Content-Type': 'application/json',
226
+ Authorization: `Bearer ${accessToken}`,
227
+ },
228
+ body: JSON.stringify({
229
+ jsonrpc: '2.0',
230
+ id: 1,
231
+ method: 'tools/list',
232
+ params: {},
233
+ }),
234
+ });
235
+ if (status !== 200 || !data?.result?.tools) {
236
+ console.warn(` Warning: tools/list returned status ${status}`);
237
+ console.warn(` Response: ${JSON.stringify(data).slice(0, 200)}`);
238
+ return [];
239
+ }
240
+ const tools = data.result.tools;
241
+ const groups = {};
242
+ for (const t of tools) {
243
+ const prefix = t.name.split('_')[0];
244
+ groups[prefix] = (groups[prefix] || 0) + 1;
245
+ }
246
+ console.log(` Found ${tools.length} tools:`);
247
+ for (const [prefix, count] of Object.entries(groups).sort()) {
248
+ console.log(` ${prefix}: ${count} tools`);
249
+ }
250
+ return tools;
251
+ }
252
+ function escapeTs(s) {
253
+ return s
254
+ .replace(/\\/g, '\\\\')
255
+ .replace(/"/g, '\\"')
256
+ .replace(/\n/g, '\\n')
257
+ .replace(/\r/g, '\\r');
258
+ }
259
+ function generateMcpToolsPlugin(gatewayUrl, accessToken, tools) {
260
+ fs.mkdirSync(TOOLS_PLUGIN_DIR, { recursive: true });
261
+ // package.json
262
+ const pkg = {
263
+ name: TOOLS_PLUGIN_ID,
264
+ version: '1.0.0',
265
+ type: 'module',
266
+ openclaw: { extensions: ['./index.ts'] },
267
+ dependencies: {},
268
+ };
269
+ fs.writeFileSync(path.join(TOOLS_PLUGIN_DIR, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
270
+ // openclaw.plugin.json
271
+ const manifest = {
272
+ id: TOOLS_PLUGIN_ID,
273
+ name: 'VirtueAI MCP Tools',
274
+ description: `${tools.length} MCP tools from VirtueAI gateway`,
275
+ configSchema: { type: 'object', properties: {} },
276
+ };
277
+ fs.writeFileSync(path.join(TOOLS_PLUGIN_DIR, 'openclaw.plugin.json'), JSON.stringify(manifest, null, 2) + '\n');
278
+ // index.ts — register each tool as a native OpenClaw tool
279
+ const mcpUrl = JSON.stringify(gatewayUrl + '/mcp');
280
+ const token = JSON.stringify(accessToken);
281
+ const toolRegistrations = tools.map((tool) => {
282
+ const name = escapeTs(tool.name);
283
+ const description = escapeTs(tool.description || `Tool: ${tool.name}`);
284
+ const schema = JSON.stringify(tool.inputSchema || { type: 'object', properties: {} });
285
+ return `
286
+ api.registerTool({
287
+ name: "${name}",
288
+ description: "${description}",
289
+ parameters: ${schema},
290
+ async execute(_id: string, params: unknown) {
291
+ try {
292
+ const response = await fetch(GATEWAY_MCP_URL, {
293
+ method: "POST",
294
+ headers: { "Content-Type": "application/json", "Authorization": AUTH_HEADER },
295
+ body: JSON.stringify({
296
+ jsonrpc: "2.0",
297
+ id: Date.now(),
298
+ method: "tools/call",
299
+ params: { name: "${name}", arguments: params }
300
+ })
301
+ });
238
302
 
239
- const GATEWAY_MCP_URL = ${JSON.stringify(gatewayUrl + '/mcp')};
240
- const TOKEN = ${JSON.stringify(accessToken)};
303
+ const result = await response.json();
241
304
 
242
- const rl = createInterface({ input: process.stdin, terminal: false });
243
- let buf = "";
305
+ if (result.error) {
306
+ return {
307
+ content: [{ type: "text", text: \`Error: \${result.error.message}\` }],
308
+ isError: true
309
+ };
310
+ }
244
311
 
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
- });
312
+ const text = result.result?.content
313
+ ?.map((c: any) => c.text ?? c.data ?? "")
314
+ .join("\\n") ?? JSON.stringify(result.result);
252
315
 
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");
316
+ return {
317
+ content: [{ type: "text", text }],
318
+ isError: false
319
+ };
320
+ } catch (err) {
321
+ return {
322
+ content: [{ type: "text", text: \`Error calling ${name}: \${err}\` }],
323
+ isError: true
324
+ };
271
325
  }
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
326
  }
282
- });
283
- req.end(body);
327
+ });`;
328
+ });
329
+ const pluginSource = `\
330
+ const GATEWAY_MCP_URL = ${mcpUrl};
331
+ const AUTH_HEADER = "Bearer " + ${token};
332
+
333
+ export default function (api: any) {
334
+ ${toolRegistrations.join('\n')}
284
335
  }
285
336
  `;
286
- fs.writeFileSync(MCP_PROXY_PATH, source, { mode: 0o755 });
287
- console.log(` Generated proxy: ${MCP_PROXY_PATH}`);
337
+ fs.writeFileSync(path.join(TOOLS_PLUGIN_DIR, 'index.ts'), pluginSource);
338
+ console.log(` Generated MCP tools plugin: ${TOOLS_PLUGIN_DIR} (${tools.length} tools)`);
288
339
  }
289
- function writeMcpConfig(gatewayUrl, accessToken, guardUuid) {
340
+ // ---------------------------------------------------------------------------
341
+ // Step 3: Write config & patch openclaw.json
342
+ // ---------------------------------------------------------------------------
343
+ function writeGatewayConfig(gatewayUrl, accessToken, guardUuid) {
290
344
  fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
291
- generateMcpProxy(gatewayUrl, accessToken);
292
345
  const config = {
293
- mcpServers: {
294
- virtueai: {
295
- type: 'stdio',
296
- command: 'node',
297
- args: [MCP_PROXY_PATH],
298
- },
299
- },
300
346
  trajectory: {
301
347
  gatewayUrl,
302
348
  guardUuid: guardUuid || process.env.VIRTUEAI_GUARD_UUID || '',
@@ -309,10 +355,7 @@ function writeMcpConfig(gatewayUrl, accessToken, guardUuid) {
309
355
  fs.writeFileSync(MCP_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
310
356
  console.log(` Written: ${MCP_CONFIG_PATH}`);
311
357
  }
312
- // ---------------------------------------------------------------------------
313
- // Step 3: Patch openclaw.json
314
- // ---------------------------------------------------------------------------
315
- function patchOpenClawConfig() {
358
+ function patchOpenClawConfig(model) {
316
359
  let config = {};
317
360
  if (fs.existsSync(OPENCLAW_CONFIG_PATH)) {
318
361
  try {
@@ -323,80 +366,64 @@ function patchOpenClawConfig() {
323
366
  config = {};
324
367
  }
325
368
  }
326
- // Ensure nested structure exists
327
369
  if (!config.agents)
328
370
  config.agents = {};
329
371
  if (!config.agents.defaults)
330
372
  config.agents.defaults = {};
331
- // Set model to claude-cli if not already set
332
- if (!config.agents.defaults.model) {
333
- config.agents.defaults.model = { primary: 'claude-cli/sonnet' };
373
+ if (model) {
374
+ config.agents.defaults.model = { primary: model };
334
375
  }
335
- // Ensure cliBackends.claude-cli exists (don't override user's existing config)
336
- if (!config.agents.defaults.cliBackends)
337
- config.agents.defaults.cliBackends = {};
338
- if (!config.agents.defaults.cliBackends['claude-cli']) {
339
- config.agents.defaults.cliBackends['claude-cli'] = {
340
- command: 'claude',
341
- };
376
+ else if (!config.agents.defaults.model ||
377
+ config.agents.defaults.model?.primary?.startsWith('claude-cli/')) {
378
+ config.agents.defaults.model = { primary: 'anthropic/claude-opus-4-6' };
379
+ }
380
+ // Clear models allowlist so users can switch to any model in OpenClaw
381
+ config.agents.defaults.models = {};
382
+ // Enable virtueai-mcp-tools plugin
383
+ if (!config.plugins)
384
+ config.plugins = {};
385
+ if (!config.plugins.entries)
386
+ config.plugins.entries = {};
387
+ config.plugins.entries[TOOLS_PLUGIN_ID] = { enabled: true };
388
+ if (!config.plugins.allow)
389
+ config.plugins.allow = [];
390
+ if (!config.plugins.allow.includes(TOOLS_PLUGIN_ID)) {
391
+ config.plugins.allow.push(TOOLS_PLUGIN_ID);
342
392
  }
343
- const cliBackend = config.agents.defaults.cliBackends['claude-cli'];
344
- // OpenClaw mergeBackendConfig: override.args ?? base.args (full replacement, not merge)
345
- if (!Array.isArray(cliBackend.args)) {
346
- cliBackend.args = ['-p', '--output-format', 'json', '--permission-mode', 'bypassPermissions'];
393
+ if (!config.plugins.installs)
394
+ config.plugins.installs = {};
395
+ config.plugins.installs[TOOLS_PLUGIN_ID] = {
396
+ source: 'path',
397
+ sourcePath: TOOLS_PLUGIN_DIR,
398
+ installPath: TOOLS_PLUGIN_DIR,
399
+ version: '1.0.0',
400
+ installedAt: new Date().toISOString(),
401
+ };
402
+ // Add plugin tools to agent allowlist (required for OpenClaw to expose them)
403
+ // Follows the pattern from DecodingTrust-Agent:
404
+ // agents.list[{id: "main", tools: {allow: ["group:plugins", pluginId]}}]
405
+ if (!config.agents.list)
406
+ config.agents.list = [];
407
+ let mainAgent = config.agents.list.find((a) => a.id === 'main');
408
+ if (!mainAgent) {
409
+ mainAgent = { id: 'main' };
410
+ config.agents.list.push(mainAgent);
347
411
  }
348
- const mcpIdx = cliBackend.args.indexOf('--mcp-config');
349
- if (mcpIdx !== -1) {
350
- cliBackend.args[mcpIdx + 1] = MCP_CONFIG_PATH;
412
+ if (!mainAgent.tools)
413
+ mainAgent.tools = {};
414
+ if (!mainAgent.tools.allow)
415
+ mainAgent.tools.allow = [];
416
+ const allowlist = mainAgent.tools.allow;
417
+ if (!allowlist.includes('group:plugins')) {
418
+ allowlist.push('group:plugins');
351
419
  }
352
- else {
353
- cliBackend.args.push('--mcp-config', MCP_CONFIG_PATH);
420
+ if (!allowlist.includes(TOOLS_PLUGIN_ID)) {
421
+ allowlist.push(TOOLS_PLUGIN_ID);
354
422
  }
355
- // Write back
356
423
  fs.writeFileSync(OPENCLAW_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
357
424
  console.log(` Patched: ${OPENCLAW_CONFIG_PATH}`);
358
425
  }
359
426
  // ---------------------------------------------------------------------------
360
- // Step 4: Verify connection
361
- // ---------------------------------------------------------------------------
362
- async function verifyConnection(gatewayUrl, accessToken) {
363
- console.log(' Verifying token with gateway...');
364
- const { status, data } = await fetchJson(`${gatewayUrl}/mcp`, {
365
- method: 'POST',
366
- headers: {
367
- 'Content-Type': 'application/json',
368
- Authorization: `Bearer ${accessToken}`,
369
- },
370
- body: JSON.stringify({
371
- jsonrpc: '2.0',
372
- id: 1,
373
- method: 'tools/list',
374
- params: {},
375
- }),
376
- });
377
- if (status === 200 && data?.result?.tools) {
378
- const tools = data.result.tools;
379
- const toolCount = tools.length;
380
- // Group by prefix
381
- const groups = {};
382
- for (const t of tools) {
383
- const prefix = t.name.split('_')[0];
384
- groups[prefix] = (groups[prefix] || 0) + 1;
385
- }
386
- console.log(` Verified! ${toolCount} tools available:`);
387
- for (const [prefix, count] of Object.entries(groups).sort()) {
388
- console.log(` ${prefix}: ${count} tools`);
389
- }
390
- return toolCount;
391
- }
392
- else {
393
- console.warn(` Warning: verification returned status ${status}`);
394
- console.warn(` Response: ${JSON.stringify(data).slice(0, 200)}`);
395
- console.warn(' Token was saved but may not work yet.');
396
- return 0;
397
- }
398
- }
399
- // ---------------------------------------------------------------------------
400
427
  // Main
401
428
  // ---------------------------------------------------------------------------
402
429
  async function main() {
@@ -409,50 +436,67 @@ Usage:
409
436
 
410
437
  Options:
411
438
  --gateway-url <url> Gateway URL (default: ${DEFAULT_GATEWAY_URL})
439
+ --model <model> Model to use (e.g. openai/gpt-4o, anthropic/claude-sonnet-4-5)
412
440
  --guard-uuid <uuid> Guard UUID for trajectory recording (or set VIRTUEAI_GUARD_UUID)
413
441
  --help Show this help message
414
442
 
415
443
  What it does:
416
444
  1. Opens browser for OAuth login
417
- 2. Saves MCP config to ~/.openclaw/mcp-gateway.json
418
- 3. Patches ~/.openclaw/openclaw.json to use the gateway
419
- 4. Installs trajectory plugin for full session recording
420
- 5. Verifies connection by listing available tools
445
+ 2. Fetches all MCP tools from the gateway
446
+ 3. Generates a native OpenClaw plugin wrapping each tool
447
+ 4. Patches ~/.openclaw/openclaw.json to enable the plugin
448
+ 5. Installs trajectory plugin for full session recording
449
+
450
+ Supported models:
451
+ Any embedded model supported by OpenClaw (NOT claude-cli).
452
+ Examples: openai/gpt-4o, anthropic/claude-sonnet-4-5
421
453
  `);
422
454
  process.exit(0);
423
455
  }
424
456
  let gatewayUrl = getArg('gateway-url') || DEFAULT_GATEWAY_URL;
457
+ const model = getArg('model');
425
458
  const guardUuid = getArg('guard-uuid') || process.env.VIRTUEAI_GUARD_UUID;
426
- // Strip /mcp suffix and normalize to lowercase
427
459
  gatewayUrl = gatewayUrl.replace(/\/mcp\/?$/, '').toLowerCase();
428
460
  console.log('\n VirtueAI Gateway Connect\n');
429
461
  console.log(` Gateway: ${gatewayUrl}`);
462
+ if (model)
463
+ console.log(` Model: ${model}`);
430
464
  // Step 1: Authenticate
431
465
  const { accessToken } = await authenticate(gatewayUrl);
432
- // Step 2: Write MCP config
466
+ // Step 2: Fetch tools & generate native plugin
433
467
  console.log('\n Configuring OpenClaw...');
434
- writeMcpConfig(gatewayUrl, accessToken, guardUuid);
435
- // Step 3: Patch openclaw.json
436
- patchOpenClawConfig();
437
- // Step 4: Install trajectory plugin
468
+ const tools = await fetchToolsList(gatewayUrl, accessToken);
469
+ if (tools.length === 0) {
470
+ console.error('\n Error: No tools found on gateway. Token may be invalid.');
471
+ process.exit(1);
472
+ }
473
+ generateMcpToolsPlugin(gatewayUrl, accessToken, tools);
474
+ // Step 3: Write gateway config (for trajectory plugin)
475
+ writeGatewayConfig(gatewayUrl, accessToken, guardUuid);
476
+ // Step 4: Patch openclaw.json (enable tools plugin + set model)
477
+ patchOpenClawConfig(model);
478
+ // Step 5: Install trajectory plugin
438
479
  console.log('\n Setting up trajectory recording...');
439
480
  generateTrajectoryPlugin(guardUuid);
440
481
  enableTrajectoryPlugin();
441
- // Step 5: Verify
442
- console.log('');
443
- const toolCount = await verifyConnection(gatewayUrl, accessToken);
444
482
  // Done
483
+ const modelDisplay = model || 'existing (unchanged)';
445
484
  console.log(`
446
485
  Done! OpenClaw is now connected to VirtueAI MCP gateway.
447
- ${toolCount} tools available across the gateway.
486
+ ${tools.length} tools registered as native OpenClaw tools.
487
+ Model: ${modelDisplay}
448
488
  Trajectory recording enabled (via virtueai-trajectory plugin).
449
489
 
450
490
  Config files:
451
491
  ${MCP_CONFIG_PATH}
452
492
  ${OPENCLAW_CONFIG_PATH}
493
+ ${TOOLS_PLUGIN_DIR}
453
494
 
454
495
  Start using it:
455
496
  openclaw agent --local --message "What tools do you have?"
497
+
498
+ To use a different model:
499
+ npx @virtue-ai/gateway-connect --gateway-url ${gatewayUrl} --model openai/gpt-4o
456
500
  `);
457
501
  process.exit(0);
458
502
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@virtue-ai/gateway-connect",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "One-command setup to connect OpenClaw to VirtueAI MCP gateway",
5
5
  "type": "module",
6
6
  "files": [