aai-gateway 0.6.0 → 0.7.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 +25 -57
- package/dist/cli.js +100 -329
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-C-9LuKWE.js → server-alVv1KNr.js} +2841 -2097
- package/dist/server-alVv1KNr.js.map +1 -0
- package/package.json +1 -1
- package/dist/server-C-9LuKWE.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
# AAI Gateway
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Why AAI Gateway
|
|
4
4
|
|
|
5
|
-
AAI
|
|
5
|
+
AAI stands for **Agent App Interface**.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Three Pain Points of MCP/Skill Configuration
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**1. Context Token Waste**: Every MCP server injects its schema, descriptions, and tool lists into the prompt. As you add more servers, the model spends more tokens understanding tools than executing tasks.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**2. Config Cannot Be Shared Across Agents**: MCP/Skill configured in Claude Code cannot be directly used in OpenCode or Codex. You have to configure it separately for each agent tool.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**3. Requires Agent Restart After Installation**: Traditionally, adding a new MCP or Skill requires restarting the agent tool to take effect.
|
|
14
14
|
|
|
15
|
-
###
|
|
15
|
+
### What AAI Gateway Solves
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- **Progressive Disclosure**: Expose app overview first, reveal tool details only when needed, avoiding context explosion
|
|
18
|
+
- **Centralized Config Management**: Import MCP/Skill once, share across all agent tools
|
|
19
|
+
- **Hot Loading**: Auto-notify agents after import, **no restart required**
|
|
20
|
+
- **Natural Language Interaction**: Search, import, and manage MCPs/Skills through simple conversation
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
**MCP / Skill Description Level**: Two tiers of disclosure are provided:
|
|
22
|
-
|
|
23
|
-
- `summary` — Natural language description; good for automatic triggering
|
|
24
|
-
- `keywords` — Compact keyword set; further reduces context overhead
|
|
25
|
-
|
|
26
|
-
This allows OpenClaw (a popular personal assistant application) and similar tools that require many tools and skills to still run smoothly.
|
|
22
|
+
AAI Gateway unifies MCP servers, Skills, ACP agents, and CLI tools under one roof, making it simple and efficient for agents to discover and use software.
|
|
27
23
|
|
|
28
24
|
## How To Use
|
|
29
25
|
|
|
@@ -86,19 +82,16 @@ Main workflow: Copy a mainstream MCP config snippet into your AI tool and ask it
|
|
|
86
82
|
The AI tool will:
|
|
87
83
|
|
|
88
84
|
1. Read the MCP config you pasted
|
|
89
|
-
2.
|
|
90
|
-
3.
|
|
85
|
+
2. Inspect the downstream MCP tools through AAI Gateway
|
|
86
|
+
3. Summarize when the MCP should be used
|
|
87
|
+
4. Ask whether it should be enabled for the current agent only or for all agents
|
|
88
|
+
5. Call `mcp:import`
|
|
91
89
|
|
|
92
90
|
AAI Gateway keeps import parameters consistent with standard MCP config shapes:
|
|
93
91
|
|
|
94
92
|
- stdio MCP: `command`, `args`, `env`, `cwd`
|
|
95
93
|
- remote MCP: `url`, optional `transport`, optional `headers`
|
|
96
94
|
|
|
97
|
-
Choose an exposure mode before import:
|
|
98
|
-
|
|
99
|
-
- `summary`: Easier automatic triggering
|
|
100
|
-
- `keywords`: Leaves room for more tools, but usually needs more explicit keyword mentions
|
|
101
|
-
|
|
102
95
|
**stdio MCP Example**:
|
|
103
96
|
|
|
104
97
|
```json
|
|
@@ -128,18 +121,18 @@ Choose an exposure mode before import:
|
|
|
128
121
|
After import, AAI Gateway returns:
|
|
129
122
|
|
|
130
123
|
- The generated app id
|
|
131
|
-
- The generated `keywords`
|
|
132
124
|
- The generated `summary`
|
|
133
125
|
- The guide tool name: `app:<id>`
|
|
134
126
|
|
|
135
|
-
>
|
|
127
|
+
> AAI Gateway sends `tools/listChanged` after import. Clients that implement this notification can pick up new tools without restart.
|
|
136
128
|
|
|
137
129
|
### 4. Import a Skill
|
|
138
130
|
|
|
139
|
-
Skills are imported through the AI tool as well.
|
|
131
|
+
Skills are imported through the AI tool as well. Tell the AI tool to import a skill using AAI Gateway, then provide:
|
|
140
132
|
|
|
141
133
|
- A local skill path
|
|
142
|
-
|
|
134
|
+
|
|
135
|
+
If the skill is remote, download and extract the whole skill directory first. AAI Gateway only imports from a local directory and copies the full directory into managed storage.
|
|
143
136
|
|
|
144
137
|
**Local Skill Example**:
|
|
145
138
|
|
|
@@ -149,17 +142,7 @@ Skills are imported through the AI tool as well. Just tell the AI tool to import
|
|
|
149
142
|
}
|
|
150
143
|
```
|
|
151
144
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```json
|
|
155
|
-
{
|
|
156
|
-
"url": "https://example.com/skill"
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Like MCP import, skill import returns `app id`, `keywords`, `summary`, and the `app:<id>` guide tool name.
|
|
161
|
-
|
|
162
|
-
Restart your AI tool after import.
|
|
145
|
+
AAI Gateway derives the imported skill summary from the skill's own `SKILL.md` description. It can also generate a lightweight proxy `SKILL.md` for the current agent so the agent can discover the skill automatically.
|
|
163
146
|
|
|
164
147
|
### 5. Supported ACP Agents
|
|
165
148
|
|
|
@@ -187,7 +170,7 @@ Currently supported ACP agent types:
|
|
|
187
170
|
│ ┌─────────────────────────────────────────────────────────┐│
|
|
188
171
|
│ │ Progressive Disclosure Layer ││
|
|
189
172
|
│ │ - App-level exposure (not tool-level) ││
|
|
190
|
-
│ │ - Summary
|
|
173
|
+
│ │ - Summary-only disclosure ││
|
|
191
174
|
│ │ - Lazy tool loading on demand ││
|
|
192
175
|
│ └─────────────────────────────────────────────────────────┘│
|
|
193
176
|
│ ┌─────────────────────────────────────────────────────────┐│
|
|
@@ -197,8 +180,8 @@ Currently supported ACP agent types:
|
|
|
197
180
|
│ └─────────────────────────────────────────────────────────┘│
|
|
198
181
|
│ ┌─────────────────────────────────────────────────────────┐│
|
|
199
182
|
│ │ Discovery Layer ││
|
|
200
|
-
│ │ - Desktop Descriptors -
|
|
201
|
-
│ │ -
|
|
183
|
+
│ │ - Desktop Descriptors - Managed Imports ││
|
|
184
|
+
│ │ - Built-in Descriptors ││
|
|
202
185
|
│ └─────────────────────────────────────────────────────────┘│
|
|
203
186
|
└────────────────────────┬────────────────────────────────────┘
|
|
204
187
|
│ Native Protocol
|
|
@@ -243,7 +226,6 @@ To integrate an app with AAI Gateway, simply provide an app descriptor file (`aa
|
|
|
243
226
|
}
|
|
244
227
|
},
|
|
245
228
|
"exposure": {
|
|
246
|
-
"keywords": ["file", "filesystem", "read", "write"],
|
|
247
229
|
"summary": "Use this app when the user wants to read from or write to the local filesystem."
|
|
248
230
|
}
|
|
249
231
|
}
|
|
@@ -263,11 +245,10 @@ To integrate an app with AAI Gateway, simply provide an app descriptor file (`aa
|
|
|
263
245
|
"access": {
|
|
264
246
|
"protocol": "skill",
|
|
265
247
|
"config": {
|
|
266
|
-
"
|
|
248
|
+
"path": "/absolute/path/to/git-commit-skill"
|
|
267
249
|
}
|
|
268
250
|
},
|
|
269
251
|
"exposure": {
|
|
270
|
-
"keywords": ["git", "commit", "version control"],
|
|
271
252
|
"summary": "Use this app when the user wants to create git commits with auto-generated messages."
|
|
272
253
|
}
|
|
273
254
|
}
|
|
@@ -291,7 +272,6 @@ To integrate an app with AAI Gateway, simply provide an app descriptor file (`aa
|
|
|
291
272
|
}
|
|
292
273
|
},
|
|
293
274
|
"exposure": {
|
|
294
|
-
"keywords": ["claude", "code", "coding", "agent"],
|
|
295
275
|
"summary": "Use this app when the user wants Claude Code to perform coding tasks."
|
|
296
276
|
}
|
|
297
277
|
}
|
|
@@ -315,7 +295,6 @@ To integrate an app with AAI Gateway, simply provide an app descriptor file (`aa
|
|
|
315
295
|
}
|
|
316
296
|
},
|
|
317
297
|
"exposure": {
|
|
318
|
-
"keywords": ["example", "utility"],
|
|
319
298
|
"summary": "Use this app when the user wants to work with Example App."
|
|
320
299
|
}
|
|
321
300
|
}
|
|
@@ -355,16 +334,6 @@ If you're unsure whether an integration should be bundled, open an issue first t
|
|
|
355
334
|
|
|
356
335
|
AAI Gateway discovers apps from the following locations:
|
|
357
336
|
|
|
358
|
-
#### Web Apps
|
|
359
|
-
|
|
360
|
-
Publish at:
|
|
361
|
-
|
|
362
|
-
```
|
|
363
|
-
https://<your-host>/.well-known/aai.json
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
AAI Gateway fetches this path when the user calls `remote:discover`.
|
|
367
|
-
|
|
368
337
|
#### macOS Apps
|
|
369
338
|
|
|
370
339
|
Recommended locations:
|
|
@@ -393,7 +362,6 @@ Scanned locations:
|
|
|
393
362
|
|
|
394
363
|
- Keep descriptors small and practical
|
|
395
364
|
- Make `app.name.default` clear
|
|
396
|
-
- Keep `keywords` short and high-signal
|
|
397
365
|
- Make `summary` explain when the app should be used
|
|
398
366
|
- Put detailed capability data in the downstream protocol, not in the descriptor
|
|
399
367
|
- If your app already speaks MCP, keep the descriptor minimal and let MCP provide lazy tool details
|
package/dist/cli.js
CHANGED
|
@@ -1,279 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { _ as logger, h as createDesktopDiscovery, m as AAI_GATEWAY_VERSION, n as createGatewayServer, r as createCliCallerContextFromEnv } from "./server-alVv1KNr.js";
|
|
5
3
|
//#region src/cli.ts
|
|
6
|
-
function parseKeyValue(value, flag) {
|
|
7
|
-
const index = value.indexOf("=");
|
|
8
|
-
if (index === -1) throw new Error(`${flag} expects KEY=VALUE`);
|
|
9
|
-
return [value.slice(0, index), value.slice(index + 1)];
|
|
10
|
-
}
|
|
11
|
-
function parsePositiveInteger(value, flag) {
|
|
12
|
-
const parsed = Number(value);
|
|
13
|
-
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${flag} expects a positive integer`);
|
|
14
|
-
return parsed;
|
|
15
|
-
}
|
|
16
4
|
function parseArgs(args) {
|
|
17
5
|
const dev = args.includes("--dev");
|
|
6
|
+
const json = args.includes("--json");
|
|
18
7
|
if (args.includes("--scan")) return {
|
|
19
8
|
command: "scan",
|
|
20
|
-
dev
|
|
9
|
+
dev,
|
|
10
|
+
json
|
|
21
11
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let name;
|
|
25
|
-
let transport;
|
|
26
|
-
let url;
|
|
27
|
-
let launchCommand;
|
|
28
|
-
let timeout;
|
|
29
|
-
let launchCwd;
|
|
30
|
-
const launchArgs = [];
|
|
31
|
-
const launchEnv = {};
|
|
32
|
-
const headers = {};
|
|
33
|
-
for (let i = 2; i < args.length; i += 1) {
|
|
34
|
-
const arg = args[i];
|
|
35
|
-
const next = args[i + 1];
|
|
36
|
-
switch (arg) {
|
|
37
|
-
case "--dev": break;
|
|
38
|
-
case "--exposure":
|
|
39
|
-
case "--summary":
|
|
40
|
-
case "--keyword":
|
|
41
|
-
i += 1;
|
|
42
|
-
break;
|
|
43
|
-
case "--name":
|
|
44
|
-
name = next;
|
|
45
|
-
i += 1;
|
|
46
|
-
break;
|
|
47
|
-
case "--transport":
|
|
48
|
-
if (next !== "streamable-http" && next !== "sse") throw new Error("--transport must be streamable-http or sse");
|
|
49
|
-
transport = next;
|
|
50
|
-
i += 1;
|
|
51
|
-
break;
|
|
52
|
-
case "--url":
|
|
53
|
-
url = next;
|
|
54
|
-
i += 1;
|
|
55
|
-
break;
|
|
56
|
-
case "--command":
|
|
57
|
-
launchCommand = next;
|
|
58
|
-
i += 1;
|
|
59
|
-
break;
|
|
60
|
-
case "--timeout":
|
|
61
|
-
timeout = parsePositiveInteger(next, "--timeout");
|
|
62
|
-
i += 1;
|
|
63
|
-
break;
|
|
64
|
-
case "--arg":
|
|
65
|
-
launchArgs.push(next);
|
|
66
|
-
i += 1;
|
|
67
|
-
break;
|
|
68
|
-
case "--env": {
|
|
69
|
-
const [key, value] = parseKeyValue(next, "--env");
|
|
70
|
-
launchEnv[key] = value;
|
|
71
|
-
i += 1;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
case "--cwd":
|
|
75
|
-
launchCwd = next;
|
|
76
|
-
i += 1;
|
|
77
|
-
break;
|
|
78
|
-
case "--header": {
|
|
79
|
-
const [key, value] = parseKeyValue(next, "--header");
|
|
80
|
-
headers[key] = value;
|
|
81
|
-
i += 1;
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
default: if (arg.startsWith("--") && ![
|
|
85
|
-
"--dev",
|
|
86
|
-
"--exposure",
|
|
87
|
-
"--summary",
|
|
88
|
-
"--keyword",
|
|
89
|
-
"--name",
|
|
90
|
-
"--transport",
|
|
91
|
-
"--url",
|
|
92
|
-
"--command",
|
|
93
|
-
"--timeout",
|
|
94
|
-
"--arg",
|
|
95
|
-
"--env",
|
|
96
|
-
"--cwd",
|
|
97
|
-
"--header"
|
|
98
|
-
].includes(arg)) throw new Error(`Unknown argument: ${arg}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
command: "mcp-import",
|
|
103
|
-
dev,
|
|
104
|
-
...exposure,
|
|
105
|
-
...name ? { name } : {},
|
|
106
|
-
transport,
|
|
107
|
-
url,
|
|
108
|
-
launchCommand,
|
|
109
|
-
...timeout !== void 0 ? { timeout } : {},
|
|
110
|
-
launchArgs,
|
|
111
|
-
launchEnv,
|
|
112
|
-
launchCwd,
|
|
113
|
-
headers
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if (args[0] === "skill" && args[1] === "import") {
|
|
117
|
-
const exposure = parseRequiredExposureArgs(args.slice(2));
|
|
118
|
-
let path;
|
|
119
|
-
let url;
|
|
120
|
-
for (let i = 2; i < args.length; i += 1) {
|
|
121
|
-
const arg = args[i];
|
|
122
|
-
const next = args[i + 1];
|
|
123
|
-
switch (arg) {
|
|
124
|
-
case "--dev": break;
|
|
125
|
-
case "--exposure":
|
|
126
|
-
case "--summary":
|
|
127
|
-
case "--keyword":
|
|
128
|
-
i += 1;
|
|
129
|
-
break;
|
|
130
|
-
case "--path":
|
|
131
|
-
path = next;
|
|
132
|
-
i += 1;
|
|
133
|
-
break;
|
|
134
|
-
case "--url":
|
|
135
|
-
url = next;
|
|
136
|
-
i += 1;
|
|
137
|
-
break;
|
|
138
|
-
default: if (arg.startsWith("--") && ![
|
|
139
|
-
"--path",
|
|
140
|
-
"--url",
|
|
141
|
-
"--dev",
|
|
142
|
-
"--exposure",
|
|
143
|
-
"--summary",
|
|
144
|
-
"--keyword"
|
|
145
|
-
].includes(arg)) throw new Error(`Unknown argument: ${arg}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
command: "skill-import",
|
|
150
|
-
dev,
|
|
151
|
-
...exposure,
|
|
152
|
-
path,
|
|
153
|
-
url
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
if (args[0] === "app" && args[1] === "config") {
|
|
157
|
-
const appId = args[2];
|
|
158
|
-
if (!appId) throw new Error("Usage: aai-gateway app config <app-id>");
|
|
159
|
-
let exposure;
|
|
160
|
-
let summary;
|
|
161
|
-
const keywords = [];
|
|
162
|
-
for (let i = 3; i < args.length; i += 1) {
|
|
163
|
-
const arg = args[i];
|
|
164
|
-
const next = args[i + 1];
|
|
165
|
-
switch (arg) {
|
|
166
|
-
case "--dev": break;
|
|
167
|
-
case "--exposure":
|
|
168
|
-
if (next !== "summary" && next !== "keywords") throw new Error("--exposure must be summary or keywords");
|
|
169
|
-
exposure = next;
|
|
170
|
-
i += 1;
|
|
171
|
-
break;
|
|
172
|
-
case "--summary":
|
|
173
|
-
summary = next;
|
|
174
|
-
i += 1;
|
|
175
|
-
break;
|
|
176
|
-
case "--keyword":
|
|
177
|
-
keywords.push(next);
|
|
178
|
-
i += 1;
|
|
179
|
-
break;
|
|
180
|
-
default: if (arg.startsWith("--") && ![
|
|
181
|
-
"--dev",
|
|
182
|
-
"--exposure",
|
|
183
|
-
"--summary",
|
|
184
|
-
"--keyword"
|
|
185
|
-
].includes(arg)) throw new Error(`Unknown argument: ${arg}`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
command: "app-config",
|
|
190
|
-
dev,
|
|
191
|
-
appId,
|
|
192
|
-
...exposure ? { exposure } : {},
|
|
193
|
-
...summary ? { summary } : {},
|
|
194
|
-
...keywords.length > 0 ? { keywords } : {}
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
12
|
+
const command = normalizeCommand(args[0]);
|
|
13
|
+
if (!command) return {
|
|
198
14
|
command: "serve",
|
|
199
|
-
dev
|
|
15
|
+
dev,
|
|
16
|
+
json
|
|
200
17
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
let
|
|
204
|
-
let
|
|
205
|
-
const keywords = [];
|
|
206
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
18
|
+
let app;
|
|
19
|
+
let tool;
|
|
20
|
+
let argsJson;
|
|
21
|
+
for (let i = 1; i < args.length; i += 1) {
|
|
207
22
|
const arg = args[i];
|
|
208
23
|
const next = args[i + 1];
|
|
209
24
|
switch (arg) {
|
|
210
|
-
case "--
|
|
211
|
-
|
|
212
|
-
|
|
25
|
+
case "--dev":
|
|
26
|
+
case "--json": break;
|
|
27
|
+
case "--app":
|
|
28
|
+
app = next;
|
|
213
29
|
i += 1;
|
|
214
30
|
break;
|
|
215
|
-
case "--
|
|
216
|
-
|
|
31
|
+
case "--tool":
|
|
32
|
+
tool = next;
|
|
217
33
|
i += 1;
|
|
218
34
|
break;
|
|
219
|
-
case "--
|
|
220
|
-
|
|
35
|
+
case "--args-json":
|
|
36
|
+
argsJson = next;
|
|
221
37
|
i += 1;
|
|
222
38
|
break;
|
|
223
|
-
default:
|
|
39
|
+
default: if (arg.startsWith("--")) throw new Error(`Unknown argument: ${arg}`);
|
|
224
40
|
}
|
|
225
41
|
}
|
|
226
|
-
if (!exposure) throw new Error("Import requires --exposure summary|keywords");
|
|
227
|
-
if (!summary) throw new Error(`Import requires --summary (maximum ${EXPOSURE_LIMITS.summaryLength} characters)`);
|
|
228
|
-
if (keywords.length === 0) throw new Error(`Import requires at least one --keyword (maximum ${EXPOSURE_LIMITS.keywordCount} total)`);
|
|
229
42
|
return {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
43
|
+
command,
|
|
44
|
+
dev,
|
|
45
|
+
json,
|
|
46
|
+
app,
|
|
47
|
+
tool,
|
|
48
|
+
argsJson
|
|
235
49
|
};
|
|
236
50
|
}
|
|
51
|
+
function normalizeCommand(value) {
|
|
52
|
+
switch (value) {
|
|
53
|
+
case "list":
|
|
54
|
+
case "guide":
|
|
55
|
+
case "schema":
|
|
56
|
+
case "exec": return value;
|
|
57
|
+
default: return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
237
60
|
function printHelp() {
|
|
238
61
|
console.log(`
|
|
239
62
|
AAI Gateway
|
|
240
63
|
|
|
241
64
|
Usage:
|
|
242
65
|
aai-gateway [options]
|
|
243
|
-
aai-gateway
|
|
244
|
-
aai-gateway
|
|
245
|
-
aai-gateway
|
|
66
|
+
aai-gateway list [--json]
|
|
67
|
+
aai-gateway guide --app <app-id>
|
|
68
|
+
aai-gateway schema --tool <tool> [--app <app-id>] [--json]
|
|
69
|
+
aai-gateway exec --tool <tool> [--app <app-id>] [--args-json <json>] [--json]
|
|
246
70
|
|
|
247
71
|
Options:
|
|
248
72
|
--scan Scan for desktop descriptors and exit
|
|
249
73
|
--dev Enable development mode
|
|
74
|
+
--json Print structured JSON when available
|
|
250
75
|
--version Show version
|
|
251
76
|
--help, -h Show help
|
|
252
|
-
|
|
253
|
-
Shared metadata options:
|
|
254
|
-
--exposure MODE Required for import. One of: summary, keywords
|
|
255
|
-
--summary TEXT Required for import, max ${EXPOSURE_LIMITS.summaryLength} characters
|
|
256
|
-
--keyword VALUE Required for import and repeatable, max ${EXPOSURE_LIMITS.keywordCount} items, each max ${EXPOSURE_LIMITS.keywordLength} characters
|
|
257
|
-
|
|
258
|
-
MCP import options:
|
|
259
|
-
--name TEXT Optional app name used for display and app id generation, max ${IMPORT_LIMITS.nameLength} chars
|
|
260
|
-
--command CMD Import a local stdio MCP server, max ${IMPORT_LIMITS.commandLength} chars
|
|
261
|
-
--timeout MS Optional MCP downstream inactivity timeout in milliseconds, default 60000
|
|
262
|
-
--arg VALUE Repeatable stdio argument, max ${IMPORT_LIMITS.argCount} items, each max ${IMPORT_LIMITS.argLength} chars
|
|
263
|
-
--env KEY=VALUE Repeatable stdio environment variable, max ${IMPORT_LIMITS.envCount} entries
|
|
264
|
-
--cwd DIR Working directory for stdio launch, max ${IMPORT_LIMITS.cwdLength} chars
|
|
265
|
-
--url URL Import a remote MCP server, max ${IMPORT_LIMITS.urlLength} chars
|
|
266
|
-
--transport TYPE Remote transport: streamable-http or sse
|
|
267
|
-
--header KEY=VALUE Repeatable remote header stored in secure storage, max ${IMPORT_LIMITS.headerCount} entries
|
|
268
|
-
|
|
269
|
-
Skill import options:
|
|
270
|
-
--path DIR Import a local skill directory, max ${IMPORT_LIMITS.pathLength} chars
|
|
271
|
-
--url URL Import a remote skill root URL, max ${IMPORT_LIMITS.urlLength} chars
|
|
272
|
-
|
|
273
|
-
App config options:
|
|
274
|
-
--exposure MODE Optional. Update the recorded exposure mode
|
|
275
|
-
--summary TEXT Optional. Override the current summary
|
|
276
|
-
--keyword VALUE Optional and repeatable. Replace the current keywords
|
|
277
77
|
`);
|
|
278
78
|
}
|
|
279
79
|
async function runScan(dev) {
|
|
@@ -290,92 +90,60 @@ async function runScan(dev) {
|
|
|
290
90
|
console.log(` Summary: ${app.descriptor.exposure.summary}`);
|
|
291
91
|
}
|
|
292
92
|
}
|
|
293
|
-
async function
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
keywords: options.keywords,
|
|
310
|
-
summary: options.summary,
|
|
311
|
-
config,
|
|
312
|
-
headers: options.headers
|
|
313
|
-
});
|
|
314
|
-
console.log(`Imported MCP app: ${result.descriptor.app.name.default}`);
|
|
315
|
-
console.log(`App ID: ${result.entry.appId}`);
|
|
316
|
-
console.log(`Descriptor: ${result.entry.descriptorPath}`);
|
|
317
|
-
console.log(`Managed directory: ${getManagedAppDir(result.entry.appId)}`);
|
|
318
|
-
console.log(`Tool name after restart: app:${result.entry.appId}`);
|
|
319
|
-
console.log(`Keywords: ${result.descriptor.exposure.keywords.join(", ")}`);
|
|
320
|
-
console.log(`Summary: ${result.descriptor.exposure.summary}`);
|
|
321
|
-
console.log(`Exposure mode: ${options.exposure}`);
|
|
93
|
+
async function withServer(dev, fn) {
|
|
94
|
+
const server = await createGatewayServer({ devMode: dev });
|
|
95
|
+
await server.initialize();
|
|
96
|
+
return fn(server);
|
|
97
|
+
}
|
|
98
|
+
async function runList(options) {
|
|
99
|
+
const caller = createCliCallerContextFromEnv();
|
|
100
|
+
const tools = await withServer(options.dev, (server) => server.listToolsForCaller(caller));
|
|
101
|
+
if (options.json) {
|
|
102
|
+
console.log(JSON.stringify({ tools }, null, 2));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
for (const tool of tools) {
|
|
106
|
+
console.log(`${tool.name}`);
|
|
107
|
+
console.log(` ${tool.description}`);
|
|
108
|
+
}
|
|
322
109
|
}
|
|
323
|
-
async function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const result = await importSkill({
|
|
329
|
-
exposureMode: options.exposure,
|
|
330
|
-
keywords: options.keywords,
|
|
331
|
-
summary: options.summary,
|
|
332
|
-
path: source.path,
|
|
333
|
-
url: source.url
|
|
334
|
-
});
|
|
335
|
-
console.log(`Imported skill: ${result.descriptor.app.name.default}`);
|
|
336
|
-
console.log(`App ID: ${result.appId}`);
|
|
337
|
-
console.log(`Descriptor: ${join(getManagedAppDir(result.appId), "aai.json")}`);
|
|
338
|
-
console.log(`Skill directory: ${result.managedPath}`);
|
|
339
|
-
console.log(`Tool name after restart: app:${result.appId}`);
|
|
340
|
-
console.log(`Keywords: ${result.descriptor.exposure.keywords.join(", ")}`);
|
|
341
|
-
console.log(`Summary: ${result.descriptor.exposure.summary}`);
|
|
342
|
-
console.log(`Exposure mode: ${options.exposure}`);
|
|
110
|
+
async function runGuide(options) {
|
|
111
|
+
if (!options.app) throw new Error("guide requires --app <app-id>");
|
|
112
|
+
const caller = createCliCallerContextFromEnv();
|
|
113
|
+
const guide = await withServer(options.dev, (server) => server.getAppGuideForCaller(stripAppPrefix(options.app), caller));
|
|
114
|
+
console.log(guide);
|
|
343
115
|
}
|
|
344
|
-
async function
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
const nextDescriptor = {
|
|
349
|
-
...descriptor,
|
|
350
|
-
exposure: nextExposure
|
|
351
|
-
};
|
|
352
|
-
if (isMcpAccess(nextDescriptor.access)) await upsertMcpRegistryEntry({
|
|
353
|
-
appId: options.appId,
|
|
354
|
-
protocol: "mcp",
|
|
355
|
-
config: nextDescriptor.access.config
|
|
356
|
-
}, nextDescriptor);
|
|
357
|
-
else if (isSkillAccess(nextDescriptor.access)) await upsertSkillRegistryEntry({
|
|
358
|
-
appId: options.appId,
|
|
359
|
-
protocol: "skill",
|
|
360
|
-
config: nextDescriptor.access.config
|
|
361
|
-
}, nextDescriptor);
|
|
362
|
-
else throw new Error(`App '${options.appId}' is not an imported MCP app or imported skill`);
|
|
363
|
-
console.log(`Updated app: ${options.appId}`);
|
|
364
|
-
if (options.exposure) console.log(`Exposure mode: ${options.exposure}`);
|
|
365
|
-
console.log(`Keywords: ${nextDescriptor.exposure.keywords.join(", ")}`);
|
|
366
|
-
console.log(`Summary: ${nextDescriptor.exposure.summary}`);
|
|
116
|
+
async function runSchema(options) {
|
|
117
|
+
if (!options.tool) throw new Error("schema requires --tool <tool>");
|
|
118
|
+
const caller = createCliCallerContextFromEnv();
|
|
119
|
+
printToolResult(await withServer(options.dev, (server) => server.getSchemaForCaller(options.app ? stripAppPrefix(options.app) : void 0, options.tool, caller)), options.json);
|
|
367
120
|
}
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
});
|
|
121
|
+
async function runExec(options) {
|
|
122
|
+
if (!options.tool) throw new Error("exec requires --tool <tool>");
|
|
123
|
+
const caller = createCliCallerContextFromEnv();
|
|
124
|
+
const args = parseArgsJson(options.argsJson);
|
|
125
|
+
printToolResult(await withServer(options.dev, (server) => server.executeForCaller(options.app ? stripAppPrefix(options.app) : void 0, options.tool, args, caller)), options.json);
|
|
374
126
|
}
|
|
375
|
-
function
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
127
|
+
function stripAppPrefix(value) {
|
|
128
|
+
return value.startsWith("app:") ? value.slice(4) : value;
|
|
129
|
+
}
|
|
130
|
+
function parseArgsJson(value) {
|
|
131
|
+
if (!value) return {};
|
|
132
|
+
const parsed = JSON.parse(value);
|
|
133
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("--args-json must be a JSON object");
|
|
134
|
+
return parsed;
|
|
135
|
+
}
|
|
136
|
+
function printToolResult(result, json) {
|
|
137
|
+
if (json && result.structuredContent) {
|
|
138
|
+
console.log(JSON.stringify(result.structuredContent, null, 2));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const text = result.content?.filter((item) => item.type === "text" && typeof item.text === "string").map((item) => item.text).join("\n").trim();
|
|
142
|
+
if (text && text.length > 0) {
|
|
143
|
+
console.log(text);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (result.structuredContent) console.log(JSON.stringify(result.structuredContent, null, 2));
|
|
379
147
|
}
|
|
380
148
|
async function main() {
|
|
381
149
|
const args = process.argv.slice(2);
|
|
@@ -392,14 +160,17 @@ async function main() {
|
|
|
392
160
|
case "scan":
|
|
393
161
|
await runScan(options.dev);
|
|
394
162
|
return;
|
|
395
|
-
case "
|
|
396
|
-
await
|
|
163
|
+
case "list":
|
|
164
|
+
await runList(options);
|
|
165
|
+
return;
|
|
166
|
+
case "guide":
|
|
167
|
+
await runGuide(options);
|
|
397
168
|
return;
|
|
398
|
-
case "
|
|
399
|
-
await
|
|
169
|
+
case "schema":
|
|
170
|
+
await runSchema(options);
|
|
400
171
|
return;
|
|
401
|
-
case "
|
|
402
|
-
await
|
|
172
|
+
case "exec":
|
|
173
|
+
await runExec(options);
|
|
403
174
|
return;
|
|
404
175
|
case "serve":
|
|
405
176
|
await (await createGatewayServer({ devMode: options.dev })).start();
|