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 CHANGED
@@ -1,29 +1,25 @@
1
1
  # AAI Gateway
2
2
 
3
- ## One MCP. Many Apps. Less Context.
3
+ ## Why AAI Gateway
4
4
 
5
- AAI Gateway turns many apps, agents, skills, and MCP servers into one MCP server.
5
+ AAI stands for **Agent App Interface**.
6
6
 
7
- ## Core Values
7
+ ### Three Pain Points of MCP/Skill Configuration
8
8
 
9
- ### Value 1: Natural Language-Driven Tool Integration
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
- After installing the AAI Gateway MCP, you can quickly integrate any other MCP or skill through natural language descriptions, and control other AI Agent tools (including Claude Code, Codex, OpenCode, etc.).
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
- AAI Gateway also integrates a search tool that helps you search for official and secure MCPs and skills from authoritative, mainstream websites, and install them with a single sentence. Control of other AI Agent tools is done via Agent Client Protocol (ACP), including session management.
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
- ### Value 2: Progressive Disclosure Strategy
15
+ ### What AAI Gateway Solves
16
16
 
17
- AAI Gateway does not dump all tool descriptions into the LLM context at once. Instead, it employs a progressive disclosure strategy:
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
- **MCP Server Level**: Only the overall description of the MCP Server is exposed initially. When the LLM determines that a specific tool needs to be used, it returns tool usage guidance first. The Agent then calls the unified `aai:exec` to execute based on that guidance. `aai:exec` accepts `appId`, `tool`, and `tool args` as parameters.
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. Ask you to choose an exposure mode
90
- 3. Call `mcp:import`
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
- > **Important**: Restart your AI tool before using the newly imported tool. After restart, the imported app will appear as `app:<id>`. Use `aai:exec` to actually run the imported app's operations.
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. Just tell the AI tool to import a skill using AAI Gateway, then provide either:
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
- - A remote skill root URL that exposes `SKILL.md`
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
- **Remote Skill Example**:
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 / Keywords modes ││
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 - Web Descriptors ││
201
- │ │ - Gateway Imports - Built-in Descriptors ││
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
- "url": "https://github.com/example/git-commit-skill"
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 { A as logger, D as createDesktopDiscovery, O as getManagedAppDir, T as AAI_GATEWAY_VERSION, _ as isMcpAccess, a as buildMcpImportConfig, b as getMcpExecutor, c as importSkill, d as upsertSkillRegistryEntry, f as upsertMcpRegistryEntry, i as IMPORT_LIMITS, l as normalizeExposureInput, m as createSecureStorage, n as createGatewayServer, o as buildSkillImportSource, r as EXPOSURE_LIMITS, s as importMcpServer, u as validateImportHeaders, v as isSkillAccess } from "./server-C-9LuKWE.js";
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
- if (args[0] === "mcp" && args[1] === "import") {
23
- const exposure = parseRequiredExposureArgs(args.slice(2));
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
- function parseRequiredExposureArgs(args) {
203
- let exposure;
204
- let summary;
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 "--exposure":
211
- if (next !== "summary" && next !== "keywords") throw new Error("--exposure must be summary or keywords");
212
- exposure = next;
25
+ case "--dev":
26
+ case "--json": break;
27
+ case "--app":
28
+ app = next;
213
29
  i += 1;
214
30
  break;
215
- case "--summary":
216
- summary = next;
31
+ case "--tool":
32
+ tool = next;
217
33
  i += 1;
218
34
  break;
219
- case "--keyword":
220
- keywords.push(next);
35
+ case "--args-json":
36
+ argsJson = next;
221
37
  i += 1;
222
38
  break;
223
- default: break;
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
- exposure,
231
- ...normalizeExposureInput({
232
- keywords,
233
- summary
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 mcp import [options]
244
- aai-gateway skill import [options]
245
- aai-gateway app config <app-id> [options]
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 runMcpImport(options) {
294
- const storage = createSecureStorage();
295
- const executor = getMcpExecutor();
296
- validateImportHeaders(options.headers);
297
- const config = buildMcpImportConfig({
298
- transport: options.transport,
299
- url: options.url,
300
- command: options.launchCommand,
301
- timeout: options.timeout,
302
- args: options.launchArgs,
303
- env: options.launchEnv,
304
- cwd: options.launchCwd
305
- });
306
- const result = await importMcpServer(executor, storage, {
307
- name: options.name,
308
- exposureMode: options.exposure,
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 runSkillImport(options) {
324
- const source = buildSkillImportSource({
325
- path: options.path,
326
- url: options.url
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 runAppConfig(options) {
345
- const descriptorPath = resolveManagedDescriptorPath(options.appId);
346
- const descriptor = JSON.parse(readFileSync(descriptorPath, "utf-8"));
347
- const nextExposure = normalizeAppConfigExposure(options, descriptor.exposure);
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 normalizeAppConfigExposure(options, current) {
369
- const summary = options.summary ?? current.summary;
370
- return normalizeExposureInput({
371
- keywords: options.keywords ?? current.keywords,
372
- summary
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 resolveManagedDescriptorPath(appId) {
376
- const descriptorPath = join(getManagedAppDir(appId), "aai.json");
377
- if (existsSync(descriptorPath)) return descriptorPath;
378
- return descriptorPath;
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 "mcp-import":
396
- await runMcpImport(options);
163
+ case "list":
164
+ await runList(options);
165
+ return;
166
+ case "guide":
167
+ await runGuide(options);
397
168
  return;
398
- case "skill-import":
399
- await runSkillImport(options);
169
+ case "schema":
170
+ await runSchema(options);
400
171
  return;
401
- case "app-config":
402
- await runAppConfig(options);
172
+ case "exec":
173
+ await runExec(options);
403
174
  return;
404
175
  case "serve":
405
176
  await (await createGatewayServer({ devMode: options.dev })).start();