aai-gateway 0.4.3 → 0.5.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
@@ -14,7 +14,6 @@ Why this matters:
14
14
  **App-level exposure, not tool-level.** Tools are grouped into apps and only the app interface is visible initially. Users interact through `app:<id>` guides instead of seeing dozens of individual tools.
15
15
 
16
16
  **Two app interfaces, user chooses:**
17
-
18
17
  - `summary` — a natural language description; good for automatic triggering
19
18
  - `keywords` — a compact keyword set; further reduces context overhead when users reference tools explicitly
20
19
 
@@ -73,12 +72,55 @@ Once connected, your AI tool can use AAI Gateway tools such as:
73
72
 
74
73
  - `remote:discover`
75
74
  - `aai:exec`
75
+ - `import:search`
76
76
  - `mcp:import`
77
77
  - `skill:import`
78
78
  - `mcp:refresh`
79
79
  - `import:config`
80
80
 
81
- ### 2. Import An MCP Server
81
+ `import:search` also has a compatibility alias: `ability_search`.
82
+
83
+ ### 2. Search For MCP Servers Or Skills
84
+
85
+ If you do not already know which MCP server or skill to install, ask the AI tool to call `import:search` first.
86
+
87
+ This tool does not perform the web search for you. Instead, it:
88
+
89
+ - turns the user request into search keywords
90
+ - recommends safer mainstream sources to search first
91
+ - normalizes the agent's gathered results into a shortlist
92
+ - gives each shortlist item a temporary id for user confirmation
93
+ - routes confirmed items into existing `mcp:import` or `skill:import` flows
94
+
95
+ Recommended source order:
96
+
97
+ - Official catalogs first:
98
+ - `modelcontextprotocol/registry`
99
+ - `modelcontextprotocol/servers`
100
+ - `openai/skills`
101
+ - Community-curated GitHub lists second:
102
+ - `punkpeye/awesome-mcp-servers`
103
+ - `ComposioHQ/awesome-claude-skills`
104
+ - Higher-scrutiny sources:
105
+ - open marketplaces such as ClawHub
106
+
107
+ Important:
108
+
109
+ - The recommended list is a preferred starting point, not a hard allowlist.
110
+ - Do not casually suggest tools from random small websites.
111
+ - Outside the preferred list, inspect maintainer identity, repository activity, README quality, license visibility, and whether the source actually exposes an importable MCP config or real skill root.
112
+ - Open marketplaces such as ClawHub should be treated with extra caution. They are not default-trust sources.
113
+
114
+ If the AI tool does not already have a retrieval tool, it can first import a fetch MCP through AAI Gateway, for example:
115
+
116
+ ```json
117
+ {
118
+ "command": "npx",
119
+ "args": ["-y", "mcp-fetch-server"]
120
+ }
121
+ ```
122
+
123
+ ### 3. Import An MCP Server
82
124
 
83
125
  The main workflow is: copy a mainstream MCP config snippet into your AI tool and ask it to import that server through AAI Gateway.
84
126
 
@@ -137,7 +179,7 @@ Important:
137
179
  - After restart, the imported app will appear as `app:<id>`.
138
180
  - Use `aai:exec` to actually run the imported app’s operations.
139
181
 
140
- ### 3. Import A Skill
182
+ ### 4. Import A Skill
141
183
 
142
184
  Skills are imported through the AI tool as well.
143
185
 
@@ -169,7 +211,7 @@ Just like MCP import, skill import returns:
169
211
 
170
212
  Then restart your AI tool before using the imported skill.
171
213
 
172
- ### 4. Supported ACP Agents
214
+ ### 5. Supported ACP Agents
173
215
 
174
216
  AAI Gateway can also control app-like agents through ACP.
175
217
 
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
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-C2U35Fro.js";
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-Cz0gw4Tf.js";
3
3
  import { join } from "node:path";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  //#region src/cli.ts
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as logger, C as AcpExecutor, D as createDesktopDiscovery, E as fetchWebDescriptor, M as ConsentManager, N as createConsentDialog, P as AaiError, S as getCliExecutor, T as AAI_GATEWAY_VERSION, b as getMcpExecutor, g as getSkillExecutor, h as SkillExecutor, j as parseAaiJson, k as SimpleCache, m as createSecureStorage, n as createGatewayServer, p as FileRegistry, t as AaiGatewayServer, w as getAcpExecutor, x as CliExecutor, y as McpExecutor } from "./server-C2U35Fro.js";
1
+ import { A as logger, C as AcpExecutor, D as createDesktopDiscovery, E as fetchWebDescriptor, M as ConsentManager, N as createConsentDialog, P as AaiError, S as getCliExecutor, T as AAI_GATEWAY_VERSION, b as getMcpExecutor, g as getSkillExecutor, h as SkillExecutor, j as parseAaiJson, k as SimpleCache, m as createSecureStorage, n as createGatewayServer, p as FileRegistry, t as AaiGatewayServer, w as getAcpExecutor, x as CliExecutor, y as McpExecutor } from "./server-Cz0gw4Tf.js";
2
2
  //#region src/executors/registry.ts
3
3
  /**
4
4
  * Executor Registry
@@ -2955,6 +2955,448 @@ function validateStringRecordLength(record, field, maxItems, maxKeyLength, maxVa
2955
2955
  }
2956
2956
  }
2957
2957
  //#endregion
2958
+ //#region src/mcp/search-guidance.ts
2959
+ var IMPORT_SEARCH_TOOL_NAME = "import:search";
2960
+ var IMPORT_SEARCH_TOOL_ALIASES = ["ability_search"];
2961
+ var PREFERRED_SOURCES = [
2962
+ {
2963
+ label: "Official MCP Registry",
2964
+ url: "https://github.com/modelcontextprotocol/registry",
2965
+ tier: "official",
2966
+ reason: "Primary MCP ecosystem registry under the official MCP GitHub organization."
2967
+ },
2968
+ {
2969
+ label: "Official MCP Servers",
2970
+ url: "https://github.com/modelcontextprotocol/servers",
2971
+ tier: "official",
2972
+ reason: "Official MCP server catalog and examples maintained by the MCP organization."
2973
+ },
2974
+ {
2975
+ label: "OpenAI Skills Catalog",
2976
+ url: "https://github.com/openai/skills",
2977
+ tier: "official",
2978
+ reason: "Official public skills catalog for Codex-oriented skills."
2979
+ },
2980
+ {
2981
+ label: "awesome-mcp-servers",
2982
+ url: "https://github.com/punkpeye/awesome-mcp-servers",
2983
+ tier: "community",
2984
+ reason: "Community-curated MCP discovery list; useful for breadth, but not authoritative."
2985
+ },
2986
+ {
2987
+ label: "awesome-claude-skills",
2988
+ url: "https://github.com/ComposioHQ/awesome-claude-skills",
2989
+ tier: "community",
2990
+ reason: "Community-curated skill list; useful for discovery, but not an official catalog."
2991
+ },
2992
+ {
2993
+ label: "Open marketplaces such as ClawHub",
2994
+ url: "https://clawhub.dev",
2995
+ tier: "high-scrutiny",
2996
+ reason: "Open publishing marketplaces can contain malicious or weakly reviewed skills and are not default-trust sources."
2997
+ }
2998
+ ];
2999
+ var importSearchInputSchema = {
3000
+ type: "object",
3001
+ properties: {
3002
+ request: {
3003
+ type: "string",
3004
+ description: "Required. The user request or installation intent you want to satisfy, for example \"I need an MCP that can search GitHub issues\"."
3005
+ },
3006
+ hasRetrievalTool: {
3007
+ type: "boolean",
3008
+ description: "Optional. Set to false when the agent does not currently have a web retrieval tool. The response will include fetch-tool fallback guidance."
3009
+ },
3010
+ evidence: {
3011
+ type: "array",
3012
+ description: "Optional. Search results gathered by the agent from preferred sources. Provide this to normalize candidates and generate user-facing selection ids.",
3013
+ items: {
3014
+ type: "object",
3015
+ properties: {
3016
+ name: { type: "string" },
3017
+ type: {
3018
+ type: "string",
3019
+ enum: ["mcp", "skill"]
3020
+ },
3021
+ source: { type: "string" },
3022
+ url: { type: "string" },
3023
+ description: { type: "string" },
3024
+ stars: { type: "number" },
3025
+ popularity: { type: "string" },
3026
+ install: {
3027
+ type: "object",
3028
+ properties: {
3029
+ transport: {
3030
+ type: "string",
3031
+ enum: ["streamable-http", "sse"]
3032
+ },
3033
+ url: { type: "string" },
3034
+ path: { type: "string" },
3035
+ headers: {
3036
+ type: "object",
3037
+ additionalProperties: { type: "string" }
3038
+ },
3039
+ command: { type: "string" },
3040
+ args: {
3041
+ type: "array",
3042
+ items: { type: "string" }
3043
+ },
3044
+ env: {
3045
+ type: "object",
3046
+ additionalProperties: { type: "string" }
3047
+ },
3048
+ cwd: { type: "string" }
3049
+ },
3050
+ additionalProperties: false
3051
+ }
3052
+ },
3053
+ required: ["name", "url"],
3054
+ additionalProperties: false
3055
+ }
3056
+ },
3057
+ confirmedIds: {
3058
+ type: "array",
3059
+ description: "Optional. Candidate ids confirmed by the user. Resend the same evidence array together with these ids to generate an install handoff plan.",
3060
+ items: { type: "string" }
3061
+ }
3062
+ },
3063
+ required: ["request"],
3064
+ additionalProperties: false,
3065
+ examples: [{
3066
+ request: "Need an MCP or skill that can search GitHub issues and PRs",
3067
+ hasRetrievalTool: true
3068
+ }, {
3069
+ request: "Need a skill for release notes and changelog drafting",
3070
+ evidence: [{
3071
+ name: "Release Notes Writer",
3072
+ type: "skill",
3073
+ source: "GitHub/openai/skills",
3074
+ url: "https://github.com/openai/skills/tree/main/skills/release-notes",
3075
+ stars: 1200,
3076
+ install: { url: "https://raw.githubusercontent.com/openai/skills/main/skills/release-notes" }
3077
+ }],
3078
+ confirmedIds: ["skill-abc123"]
3079
+ }]
3080
+ };
3081
+ function parseImportSearchArguments(args) {
3082
+ const request = asTrimmedString(args?.request);
3083
+ if (!request) throw new Error(`${IMPORT_SEARCH_TOOL_NAME} requires a non-empty 'request'`);
3084
+ const evidence = parseEvidence(args?.evidence);
3085
+ const confirmedIds = parseConfirmedIds(args?.confirmedIds);
3086
+ return {
3087
+ request,
3088
+ hasRetrievalTool: args?.hasRetrievalTool === false ? false : true,
3089
+ evidence,
3090
+ confirmedIds
3091
+ };
3092
+ }
3093
+ function buildImportSearchResponse(args) {
3094
+ const lines = [];
3095
+ lines.push(`Search intent: ${args.request}`);
3096
+ lines.push("");
3097
+ lines.push("Preferred source policy:");
3098
+ lines.push(...formatSourcePolicy());
3099
+ lines.push("");
3100
+ lines.push("Suggested queries:");
3101
+ lines.push(...buildQueryPlan(args.request).map((query) => `- ${query}`));
3102
+ if (!args.hasRetrievalTool) {
3103
+ lines.push("");
3104
+ lines.push("Retrieval fallback:");
3105
+ lines.push(...formatFetchFallback());
3106
+ }
3107
+ if (args.evidence.length === 0) {
3108
+ lines.push("");
3109
+ lines.push("Next step:");
3110
+ lines.push("- Search the preferred sources above with your own retrieval tool. If you do not have one, import a fetch MCP first.");
3111
+ lines.push("- Gather candidate evidence with at least `name` and `url`. Include `type`, `source`, `stars`, `description`, and `install` hints when available.");
3112
+ lines.push(`- Call \`${IMPORT_SEARCH_TOOL_NAME}\` again with the same \`request\` plus an \`evidence\` array so the gateway can normalize a shortlist.`);
3113
+ return lines.join("\n");
3114
+ }
3115
+ const candidates = normalizeCandidates(args.evidence);
3116
+ lines.push("");
3117
+ lines.push("Candidate shortlist:");
3118
+ lines.push(...formatCandidates(candidates));
3119
+ if (args.confirmedIds.length === 0) {
3120
+ lines.push("");
3121
+ lines.push("User confirmation required:");
3122
+ lines.push("- Ask the user to confirm one or more candidate ids from the shortlist above.");
3123
+ lines.push(`- Then call \`${IMPORT_SEARCH_TOOL_NAME}\` again with the same \`request\`, the same \`evidence\`, and \`confirmedIds\`.`);
3124
+ return lines.join("\n");
3125
+ }
3126
+ const confirmed = resolveConfirmedCandidates(candidates, args.confirmedIds);
3127
+ lines.push("");
3128
+ lines.push("Installation handoff:");
3129
+ lines.push(...formatInstallHandoff(confirmed));
3130
+ lines.push("");
3131
+ lines.push("Import reminder:");
3132
+ lines.push("- After the first `mcp:import` or `skill:import` inspection call, ask the user to confirm exposure mode, keywords, and summary before completing the final import.");
3133
+ return lines.join("\n");
3134
+ }
3135
+ function normalizeCandidates(evidence) {
3136
+ const seenIds = /* @__PURE__ */ new Map();
3137
+ return evidence.map((item) => {
3138
+ const type = inferCandidateType(item);
3139
+ const source = item.source?.trim() || inferSourceLabel(item.url);
3140
+ const tier = inferSourceTier(item.url, source);
3141
+ const install = normalizeInstallHint(item.install);
3142
+ const popularity = normalizePopularity(item.stars, item.popularity);
3143
+ const baseId = `${type}-${stableHash([
3144
+ type,
3145
+ item.name,
3146
+ item.url,
3147
+ source
3148
+ ].join("|"))}`;
3149
+ const nextCount = (seenIds.get(baseId) ?? 0) + 1;
3150
+ seenIds.set(baseId, nextCount);
3151
+ return {
3152
+ id: nextCount === 1 ? baseId : `${baseId}-${nextCount}`,
3153
+ type,
3154
+ name: item.name.trim(),
3155
+ source,
3156
+ url: item.url.trim(),
3157
+ description: asOptionalTrimmedString(item.description),
3158
+ stars: typeof item.stars === "number" && Number.isFinite(item.stars) ? item.stars : void 0,
3159
+ popularity,
3160
+ tier,
3161
+ install
3162
+ };
3163
+ }).sort((a, b) => {
3164
+ const starDelta = (b.stars ?? -1) - (a.stars ?? -1);
3165
+ if (starDelta !== 0) return starDelta;
3166
+ return a.name.localeCompare(b.name);
3167
+ });
3168
+ }
3169
+ function resolveConfirmedCandidates(candidates, confirmedIds) {
3170
+ const uniqueIds = Array.from(new Set(confirmedIds.map((item) => item.trim()).filter(Boolean)));
3171
+ const matched = uniqueIds.map((id) => candidates.find((candidate) => candidate.id === id));
3172
+ const missing = uniqueIds.filter((_id, index) => !matched[index]);
3173
+ if (missing.length > 0) throw new Error(`${IMPORT_SEARCH_TOOL_NAME} received unknown confirmedIds: ${missing.join(", ")}`);
3174
+ return matched.filter((candidate) => Boolean(candidate));
3175
+ }
3176
+ function formatInstallHandoff(candidates) {
3177
+ const lines = [];
3178
+ for (const candidate of candidates) {
3179
+ lines.push(`- [${candidate.id}] ${candidate.name} | ${candidate.type} | next step: ${candidate.type === "mcp" ? "`mcp:import`" : "`skill:import`"}`);
3180
+ lines.push(` Source: ${candidate.url}`);
3181
+ if (candidate.type === "mcp") {
3182
+ const payload = buildMcpImportPayload(candidate.install);
3183
+ if (payload) {
3184
+ lines.push(" First import call payload:");
3185
+ lines.push(indentBlock(jsonBlock(payload)));
3186
+ } else lines.push(" Import status: inspect the repository docs first and extract a supported MCP config snippet before calling `mcp:import`.");
3187
+ } else {
3188
+ const payload = buildSkillImportPayload(candidate.install);
3189
+ if (payload) {
3190
+ lines.push(" First import call payload:");
3191
+ lines.push(indentBlock(jsonBlock(payload)));
3192
+ } else lines.push(" Import status: inspect the skill docs first and find a skill root `url` or local `path` before calling `skill:import`.");
3193
+ }
3194
+ }
3195
+ return lines;
3196
+ }
3197
+ function buildMcpImportPayload(install) {
3198
+ if (!install) return null;
3199
+ try {
3200
+ const payload = { ...buildMcpImportConfig({
3201
+ transport: install.transport,
3202
+ url: asOptionalTrimmedString(install.url),
3203
+ command: asOptionalTrimmedString(install.command),
3204
+ args: asStringArray$1(install.args),
3205
+ env: isStringRecord$1(install.env) ? install.env : void 0,
3206
+ cwd: asOptionalTrimmedString(install.cwd)
3207
+ }) };
3208
+ if (isStringRecord$1(install.headers)) {
3209
+ validateImportHeaders(install.headers);
3210
+ payload.headers = install.headers;
3211
+ }
3212
+ return payload;
3213
+ } catch {
3214
+ return null;
3215
+ }
3216
+ }
3217
+ function buildSkillImportPayload(install) {
3218
+ if (!install) return null;
3219
+ try {
3220
+ return { ...buildSkillImportSource({
3221
+ path: asOptionalTrimmedString(install.path),
3222
+ url: asOptionalTrimmedString(install.url)
3223
+ }) };
3224
+ } catch {
3225
+ return null;
3226
+ }
3227
+ }
3228
+ function formatCandidates(candidates) {
3229
+ return candidates.map((candidate) => {
3230
+ const popularity = candidate.popularity ? ` | popularity: ${candidate.popularity}` : "";
3231
+ const description = candidate.description ? ` | ${candidate.description}` : "";
3232
+ return `- [${candidate.id}] ${candidate.name} | ${candidate.type} | ${candidate.source} | trust: ${candidate.tier}${popularity} | ${candidate.url}${description}`;
3233
+ });
3234
+ }
3235
+ function buildQueryPlan(request) {
3236
+ const normalized = request.trim();
3237
+ return [
3238
+ normalized,
3239
+ `mcp ${normalized}`,
3240
+ `model context protocol ${normalized}`,
3241
+ `skill ${normalized}`,
3242
+ `SKILL.md ${normalized}`,
3243
+ `site:github.com ${normalized}`
3244
+ ];
3245
+ }
3246
+ function formatSourcePolicy() {
3247
+ return PREFERRED_SOURCES.map((source) => `- [${source.tier}] ${source.label}: ${source.url} — ${source.reason}`).concat([
3248
+ "- Use the list above as a preferred starting point, not a hard allowlist.",
3249
+ "- Avoid arbitrary low-trust or low-signal websites when suggesting installable tools.",
3250
+ "- Outside the preferred list, verify maintainer identity, repository activity, README quality, license visibility, and whether the source exposes a real import path or config."
3251
+ ]);
3252
+ }
3253
+ function formatFetchFallback() {
3254
+ return [
3255
+ "- You said no retrieval tool is available. Import a fetch MCP before searching remote sources if needed.",
3256
+ "- Recommended first import call:",
3257
+ indentBlock(jsonBlock({
3258
+ command: "npx",
3259
+ args: ["-y", "mcp-fetch-server"]
3260
+ })),
3261
+ "- Optional MCP config details for the underlying server:",
3262
+ indentBlock(jsonBlock({
3263
+ type: "local",
3264
+ command: [
3265
+ "npx",
3266
+ "-y",
3267
+ "mcp-fetch-server"
3268
+ ],
3269
+ enabled: true,
3270
+ timeout: 5e4,
3271
+ environment: { DEFAULT_LIMIT: "50000" }
3272
+ }))
3273
+ ];
3274
+ }
3275
+ function parseEvidence(value) {
3276
+ if (value === void 0) return [];
3277
+ if (!Array.isArray(value)) throw new Error(`${IMPORT_SEARCH_TOOL_NAME} expected 'evidence' to be an array`);
3278
+ return value.map((item, index) => parseEvidenceItem(item, index));
3279
+ }
3280
+ function parseEvidenceItem(value, index) {
3281
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${IMPORT_SEARCH_TOOL_NAME} evidence[${index}] must be an object`);
3282
+ const item = value;
3283
+ const name = asTrimmedString(item.name);
3284
+ const url = asTrimmedString(item.url);
3285
+ if (!name || !url) throw new Error(`${IMPORT_SEARCH_TOOL_NAME} evidence[${index}] requires non-empty 'name' and 'url'`);
3286
+ return {
3287
+ name,
3288
+ type: item.type === "mcp" || item.type === "skill" ? item.type : void 0,
3289
+ source: asOptionalTrimmedString(item.source),
3290
+ url,
3291
+ description: asOptionalTrimmedString(item.description),
3292
+ stars: asOptionalNumber(item.stars),
3293
+ popularity: asOptionalTrimmedString(item.popularity),
3294
+ install: parseInstallHint(item.install, index)
3295
+ };
3296
+ }
3297
+ function parseInstallHint(value, index) {
3298
+ if (value === void 0) return;
3299
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${IMPORT_SEARCH_TOOL_NAME} evidence[${index}].install must be an object`);
3300
+ const install = value;
3301
+ return normalizeInstallHint({
3302
+ transport: install.transport === "streamable-http" || install.transport === "sse" ? install.transport : void 0,
3303
+ url: asOptionalTrimmedString(install.url),
3304
+ path: asOptionalTrimmedString(install.path),
3305
+ headers: isStringRecord$1(install.headers) ? install.headers : void 0,
3306
+ command: asOptionalTrimmedString(install.command),
3307
+ args: asStringArray$1(install.args),
3308
+ env: isStringRecord$1(install.env) ? install.env : void 0,
3309
+ cwd: asOptionalTrimmedString(install.cwd)
3310
+ });
3311
+ }
3312
+ function parseConfirmedIds(value) {
3313
+ if (value === void 0) return [];
3314
+ if (!Array.isArray(value)) throw new Error(`${IMPORT_SEARCH_TOOL_NAME} expected 'confirmedIds' to be an array`);
3315
+ return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
3316
+ }
3317
+ function inferCandidateType(item) {
3318
+ if (item.type) return item.type;
3319
+ if (item.install?.path) return "skill";
3320
+ if (item.install?.command || item.install?.transport || item.install?.headers) return "mcp";
3321
+ const combined = `${item.name} ${item.source ?? ""} ${item.url} ${item.description ?? ""}`.toLowerCase();
3322
+ if (combined.includes("skill")) return "skill";
3323
+ if (combined.includes("mcp")) return "mcp";
3324
+ if (item.url.toLowerCase().includes("/skills/")) return "skill";
3325
+ return "mcp";
3326
+ }
3327
+ function inferSourceLabel(url) {
3328
+ try {
3329
+ const parsed = new URL(url);
3330
+ if (parsed.hostname === "github.com") {
3331
+ const segments = parsed.pathname.split("/").filter(Boolean);
3332
+ if (segments.length >= 2) return `GitHub/${segments[0]}/${segments[1]}`;
3333
+ }
3334
+ return parsed.hostname;
3335
+ } catch {
3336
+ return "unclassified";
3337
+ }
3338
+ }
3339
+ function inferSourceTier(url, source) {
3340
+ const value = `${url} ${source}`.toLowerCase();
3341
+ if (value.includes("github.com/modelcontextprotocol/registry") || value.includes("github.com/modelcontextprotocol/servers") || value.includes("github.com/openai/skills")) return "official";
3342
+ if (value.includes("github.com/punkpeye/awesome-mcp-servers") || value.includes("github.com/composiohq/awesome-claude-skills")) return "community";
3343
+ if (value.includes("clawhub")) return "high-scrutiny";
3344
+ return "unclassified";
3345
+ }
3346
+ function normalizePopularity(stars, popularity) {
3347
+ if (typeof stars === "number" && Number.isFinite(stars)) return `${formatCompactNumber(stars)} GitHub stars`;
3348
+ return asOptionalTrimmedString(popularity);
3349
+ }
3350
+ function formatCompactNumber(value) {
3351
+ if (value >= 1e3) return `${Math.round(value / 1e3 * 10) / 10}k`;
3352
+ return String(value);
3353
+ }
3354
+ function normalizeInstallHint(install) {
3355
+ if (!install) return;
3356
+ const next = {
3357
+ transport: install.transport,
3358
+ url: asOptionalTrimmedString(install.url),
3359
+ path: asOptionalTrimmedString(install.path),
3360
+ headers: isStringRecord$1(install.headers) ? install.headers : void 0,
3361
+ command: asOptionalTrimmedString(install.command),
3362
+ args: asStringArray$1(install.args),
3363
+ env: isStringRecord$1(install.env) ? install.env : void 0,
3364
+ cwd: asOptionalTrimmedString(install.cwd)
3365
+ };
3366
+ if (Object.values(next).every((value) => value === void 0 || Array.isArray(value) && value.length === 0)) return;
3367
+ return next;
3368
+ }
3369
+ function stableHash(value) {
3370
+ let hash = 2166136261;
3371
+ for (let index = 0; index < value.length; index += 1) {
3372
+ hash ^= value.charCodeAt(index);
3373
+ hash = Math.imul(hash, 16777619);
3374
+ }
3375
+ return (hash >>> 0).toString(36).slice(0, 6);
3376
+ }
3377
+ function jsonBlock(value) {
3378
+ return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
3379
+ }
3380
+ function indentBlock(value) {
3381
+ return value.split("\n").map((line) => ` ${line}`).join("\n");
3382
+ }
3383
+ function asTrimmedString(value) {
3384
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3385
+ }
3386
+ function asOptionalTrimmedString(value) {
3387
+ return asTrimmedString(value);
3388
+ }
3389
+ function asOptionalNumber(value) {
3390
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3391
+ }
3392
+ function asStringArray$1(value) {
3393
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
3394
+ }
3395
+ function isStringRecord$1(value) {
3396
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3397
+ return Object.entries(value).every(([key, item]) => typeof key === "string" && typeof item === "string");
3398
+ }
3399
+ //#endregion
2958
3400
  //#region src/mcp/task-runner.ts
2959
3401
  var McpTaskRunner = class {
2960
3402
  activeTasks = /* @__PURE__ */ new Map();
@@ -3378,6 +3820,16 @@ var AaiGatewayServer = class {
3378
3820
  }
3379
3821
  }
3380
3822
  });
3823
+ tools.push({
3824
+ name: IMPORT_SEARCH_TOOL_NAME,
3825
+ description: "Plan discovery for MCP servers and skills, normalize agent-gathered search results into a shortlist, and generate install handoff guidance that routes to existing import tools.",
3826
+ inputSchema: importSearchInputSchema
3827
+ });
3828
+ for (const alias of IMPORT_SEARCH_TOOL_ALIASES) tools.push({
3829
+ name: alias,
3830
+ description: `Alias for \`${IMPORT_SEARCH_TOOL_NAME}\`. Use it to get discovery guidance, shortlist candidates, and hand off confirmed items to existing import tools.`,
3831
+ inputSchema: importSearchInputSchema
3832
+ });
3381
3833
  return { tools };
3382
3834
  });
3383
3835
  this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
@@ -3395,6 +3847,7 @@ var AaiGatewayServer = class {
3395
3847
  if (name === "mcp:import") return this.handleMcpImport(args);
3396
3848
  if (name === "skill:import") return this.handleSkillImport(args);
3397
3849
  if (name === "import:config") return this.handleImportConfig(args);
3850
+ if (name === "import:search" || IMPORT_SEARCH_TOOL_ALIASES.includes(name)) return this.handleImportSearch(args);
3398
3851
  throw new AaiError("UNKNOWN_TOOL", `Unknown tool: ${name}`);
3399
3852
  });
3400
3853
  }
@@ -3592,6 +4045,16 @@ var AaiGatewayServer = class {
3592
4045
  ].join("\n")
3593
4046
  }] };
3594
4047
  }
4048
+ async handleImportSearch(args) {
4049
+ try {
4050
+ return { content: [{
4051
+ type: "text",
4052
+ text: buildImportSearchResponse(parseImportSearchArguments(args))
4053
+ }] };
4054
+ } catch (err) {
4055
+ throw new AaiError("INVALID_REQUEST", err instanceof Error ? err.message : String(err));
4056
+ }
4057
+ }
3595
4058
  async loadGuideDetail(localId, descriptor) {
3596
4059
  try {
3597
4060
  return await this.loadLayer3Detail(localId, descriptor);
@@ -4123,4 +4586,4 @@ function createStaticDetail(descriptor, err) {
4123
4586
  //#endregion
4124
4587
  export { logger as A, AcpExecutor as C, createDesktopDiscovery as D, fetchWebDescriptor as E, ConsentManager as M, createConsentDialog as N, getManagedAppDir as O, AaiError as P, getCliExecutor as S, AAI_GATEWAY_VERSION as T, isMcpAccess as _, buildMcpImportConfig as a, getMcpExecutor as b, importSkill as c, upsertSkillRegistryEntry as d, upsertMcpRegistryEntry as f, getSkillExecutor as g, SkillExecutor as h, IMPORT_LIMITS as i, parseAaiJson as j, SimpleCache as k, normalizeExposureInput as l, createSecureStorage as m, createGatewayServer as n, buildSkillImportSource as o, FileRegistry as p, EXPOSURE_LIMITS as r, importMcpServer as s, AaiGatewayServer as t, validateImportHeaders as u, isSkillAccess as v, getAcpExecutor as w, CliExecutor as x, McpExecutor as y };
4125
4588
 
4126
- //# sourceMappingURL=server-C2U35Fro.js.map
4589
+ //# sourceMappingURL=server-Cz0gw4Tf.js.map