@ycodium-ai/plugin-connector-registry 0.2.3710 → 0.2.3799

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,17 +1,33 @@
1
1
  # @ycodium-ai/plugin-connector-registry
2
2
 
3
- Lists MCP servers from the catalog address you set, in the Catalog tab under Settings → Plugins. Installing one adds it to this machine's MCP servers.
3
+ Lets you pick MCP servers from the official MCP registry
4
+ (`https://registry.modelcontextprotocol.io`) in the Add server dialog under
5
+ machine Settings → MCP & skills. Anyone can list a server in that registry and
6
+ entries are not reviewed.
4
7
 
5
8
  ## What it does
6
9
 
7
- The plugin exposes a `search` command. It merges a catalog shipped with the
8
- package with a remote catalog, then filters locally. The remote URL comes from
9
- the entry config (`url`), which the host fills from `YCODIUM_MCP_REGISTRY_URL`
10
- and which the settings card can override.
10
+ The plugin exposes a `search` command and an agent tool of the same name
11
+ (`connector-registry_search`). Both query `GET <registry>/v0/servers` with
12
+ `search`, `limit` and `version=latest`, keep entries the registry marks active
13
+ and latest, and turn each into a draft the dialog can fill in:
11
14
 
12
- If no URL is configured and the shipped catalog is empty, `search` returns
13
- `{ status: "not-configured" }`. A fetch failure falls back to cache, then to
14
- the shipped copy, and only fails when there is nothing to show.
15
+ - an npm package runs as `npx -y <package>@<version>`, a PyPI package as
16
+ `uvx <package>==<version>`; a remote server becomes an HTTP or SSE transport
17
+ with its headers;
18
+ - secrets are left empty, and every value the user still has to supply is
19
+ listed in `needs`;
20
+ - entries that only ship other package types (OCI images, NuGet, MCPB) come
21
+ back as `manual`.
22
+
23
+ The registry address comes from the entry config (`url`), which the host fills
24
+ from `YCODIUM_MCP_REGISTRY_URL` and the settings card can override. When it is
25
+ empty the official registry is used. Another registry must serve the same
26
+ `/v0/servers` interface.
27
+
28
+ Results are cached per query for ten minutes. When the registry cannot be
29
+ reached, `search` returns the last cached result for that query with
30
+ `stale: true`, and fails only when there is none.
15
31
 
16
32
  ## Install
17
33
 
@@ -23,9 +39,9 @@ This plugin declares no extra permissions. Outbound fetches use the host HTTP
23
39
  client, including the machine proxy.
24
40
 
25
41
  Ycodium ships it enabled as `core.connector-registry`. Disabling it hides the
26
- directory search. Uninstalling it writes a removal to the home patch so a
27
- restart does not bring it back. Reinstalling the unversioned bundled package
28
- restores the same entry without an npm fetch.
42
+ registry picker in the Add server dialog. Uninstalling it writes a removal to
43
+ the home patch so a restart does not bring it back. Reinstalling the
44
+ unversioned bundled package restores the same entry without an npm fetch.
29
45
 
30
46
  ## Developing
31
47
 
package/dist/index.js CHANGED
@@ -1,195 +1,340 @@
1
1
  // src/index.ts
2
2
  import { definePlugin } from "@ycodium-ai/plugin-api";
3
3
 
4
- // src/catalog.ts
5
- var BUNDLED_CATALOG = [];
6
- var mergeCatalog = (bundled, fetched) => {
7
- const byId = /* @__PURE__ */ new Map();
8
- for (const entry of bundled) byId.set(entry.catalogId, entry);
9
- for (const entry of fetched) byId.set(entry.catalogId, entry);
10
- return [...byId.values()].sort((left, right) => left.name.localeCompare(right.name));
11
- };
12
- var filterCatalog = (entries, query) => {
13
- const needle = query.trim().toLowerCase();
14
- if (needle.length === 0) return entries;
15
- return entries.filter(
16
- (entry) => entry.name.toLowerCase().includes(needle) || entry.description.toLowerCase().includes(needle)
17
- );
4
+ // src/registry.ts
5
+ var DEFAULT_REGISTRY_URL = "https://registry.modelcontextprotocol.io";
6
+ var OFFICIAL_META_KEY = "io.modelcontextprotocol.registry/official";
7
+ var FALLBACK_SERVER_NAME = "mcp-server";
8
+ var RESERVED_SERVER_NAME = "ycodium";
9
+ var PLACEHOLDER = /\{[^{}]*\}/;
10
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
11
+ var str = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
12
+ var records = (value) => Array.isArray(value) ? value.filter(isRecord) : [];
13
+ var serversEndpoint = (base) => {
14
+ const trimmed = base.trim().replace(/\/+$/, "");
15
+ return trimmed.endsWith("/servers") ? trimmed : `${trimmed}/v0/servers`;
16
+ };
17
+ var searchUrl = (base, query, limit) => {
18
+ const url = new URL(serversEndpoint(base));
19
+ url.searchParams.set("limit", String(limit));
20
+ url.searchParams.set("version", "latest");
21
+ const needle = query.trim();
22
+ if (needle.length > 0) url.searchParams.set("search", needle);
23
+ return url.toString();
24
+ };
25
+ var suggestServerName = (registryName) => {
26
+ const last = registryName.split("/").pop() ?? "";
27
+ const slug = last.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^[^a-z0-9]+/, "").slice(0, 64);
28
+ return slug.length === 0 || slug === RESERVED_SERVER_NAME ? FALLBACK_SERVER_NAME : slug;
29
+ };
30
+ var argumentParts = (argument) => {
31
+ const required = argument["isRequired"] === true;
32
+ const value = str(argument["value"]) ?? (required ? str(argument["default"]) : void 0);
33
+ if (value === void 0 && !required) return [];
34
+ if (argument["type"] === "named") {
35
+ const name = str(argument["name"]);
36
+ if (name === void 0) return [];
37
+ const flag = name.startsWith("-") ? name : `--${name}`;
38
+ return [flag, value ?? `<${str(argument["valueHint"]) ?? name.replace(/^-+/, "")}>`];
39
+ }
40
+ return [value ?? `<${str(argument["valueHint"]) ?? str(argument["name"]) ?? "value"}>`];
41
+ };
42
+ var argumentsOf = (value) => records(value).flatMap(argumentParts);
43
+ var WHOLE_ARGUMENT_PLACEHOLDER = /^<[^<>]+>$/;
44
+ var TEMPLATES = /\{[^{}]*\}/g;
45
+ var argumentNeeds = (args) => args.flatMap(
46
+ (arg) => WHOLE_ARGUMENT_PLACEHOLDER.test(arg) ? [arg] : arg.match(TEMPLATES) ?? []
47
+ );
48
+ var fillDeclared = (declared) => {
49
+ const values = {};
50
+ const needs = [];
51
+ const optional = [];
52
+ for (const entry of records(declared)) {
53
+ const name = str(entry["name"]);
54
+ if (name === void 0) continue;
55
+ const secret = entry["isSecret"] === true;
56
+ const required = entry["isRequired"] === true;
57
+ const given = str(entry["value"]) ?? str(entry["default"]);
58
+ if (!secret && !required && given === void 0) continue;
59
+ const value = secret ? given !== void 0 && PLACEHOLDER.test(given) ? given : "" : given ?? "";
60
+ values[name] = value;
61
+ if (required || PLACEHOLDER.test(value)) needs.push(name);
62
+ else if (secret) optional.push(name);
63
+ }
64
+ return { values, needs, optional };
65
+ };
66
+ var packageSpec = (identifier, version, separator) => version === void 0 || version === "latest" ? identifier : `${identifier}${separator}${version}`;
67
+ var stdioSetup = (pkg, name, runner) => {
68
+ const identifier = str(pkg["identifier"]);
69
+ if (identifier === void 0) return null;
70
+ const spec = packageSpec(identifier, str(pkg["version"]), runner === "npx" ? "@" : "==");
71
+ const runtime = argumentsOf(pkg["runtimeArguments"]);
72
+ const confirm = runner === "npx" && !runtime.includes("-y") && !runtime.includes("--yes");
73
+ const args = [
74
+ ...confirm ? ["-y"] : [],
75
+ ...runtime,
76
+ spec,
77
+ ...argumentsOf(pkg["packageArguments"])
78
+ ];
79
+ const env = fillDeclared(pkg["environmentVariables"]);
80
+ return {
81
+ kind: "ready",
82
+ name,
83
+ transport: {
84
+ type: "stdio",
85
+ command: runner,
86
+ args,
87
+ ...Object.keys(env.values).length > 0 ? { env: env.values } : {}
88
+ },
89
+ needs: [...env.needs, ...argumentNeeds(args)],
90
+ optional: env.optional
91
+ };
92
+ };
93
+ var remoteSetup = (remote, type, name) => {
94
+ const url = str(remote["url"]);
95
+ if (url === void 0 || PLACEHOLDER.test(url) || !/^https?:\/\//.test(url)) return null;
96
+ const headers = fillDeclared(remote["headers"]);
97
+ return {
98
+ kind: "ready",
99
+ name,
100
+ transport: {
101
+ type,
102
+ url,
103
+ ...Object.keys(headers.values).length > 0 ? { headers: headers.values } : {}
104
+ },
105
+ needs: headers.needs,
106
+ optional: headers.optional
107
+ };
108
+ };
109
+ var isStdioPackage = (pkg, registryType) => pkg["registryType"] === registryType && isRecord(pkg["transport"]) && pkg["transport"]["type"] === "stdio";
110
+ var setupOf = (server) => {
111
+ const name = suggestServerName(str(server["name"]) ?? "");
112
+ const packages = records(server["packages"]);
113
+ const remotes = records(server["remotes"]);
114
+ const candidates = [
115
+ ...packages.filter((pkg) => isStdioPackage(pkg, "npm")).map((pkg) => () => stdioSetup(pkg, name, "npx")),
116
+ ...remotes.filter((remote) => remote["type"] === "streamable-http").map((remote) => () => remoteSetup(remote, "http", name)),
117
+ ...remotes.filter((remote) => remote["type"] === "sse").map((remote) => () => remoteSetup(remote, "sse", name)),
118
+ ...packages.filter((pkg) => isStdioPackage(pkg, "pypi")).map((pkg) => () => stdioSetup(pkg, name, "uvx"))
119
+ ];
120
+ for (const candidate of candidates) {
121
+ const setup = candidate();
122
+ if (setup !== null) return setup;
123
+ }
124
+ return { kind: "manual" };
125
+ };
126
+ var httpsUrl = (value) => {
127
+ const url = str(value);
128
+ return url !== void 0 && url.startsWith("https://") ? url : void 0;
129
+ };
130
+ var isCurrent = (meta) => {
131
+ if (!isRecord(meta)) return false;
132
+ const official = meta[OFFICIAL_META_KEY];
133
+ return isRecord(official) && official["status"] === "active" && official["isLatest"] === true;
134
+ };
135
+ var toServer = (entry) => {
136
+ const server = entry["server"];
137
+ if (!isRecord(server) || !isCurrent(entry["_meta"])) return null;
138
+ const id = str(server["name"]);
139
+ const version = str(server["version"]);
140
+ if (id === void 0 || version === void 0) return null;
141
+ const slash = id.indexOf("/");
142
+ const repository = isRecord(server["repository"]) ? server["repository"]["url"] : void 0;
143
+ const homepage = httpsUrl(server["websiteUrl"]) ?? httpsUrl(repository);
144
+ return {
145
+ id,
146
+ title: str(server["title"]) ?? id.slice(slash + 1),
147
+ publisher: slash > 0 ? id.slice(0, slash) : id,
148
+ description: str(server["description"]) ?? "",
149
+ version,
150
+ ...homepage === void 0 ? {} : { homepage },
151
+ setup: setupOf(server)
152
+ };
153
+ };
154
+ var parseRegistryResponse = (body) => {
155
+ if (!isRecord(body) || !Array.isArray(body["servers"])) return null;
156
+ return records(body["servers"]).flatMap((entry) => {
157
+ const server = toServer(entry);
158
+ return server === null ? [] : [server];
159
+ });
18
160
  };
19
161
 
20
162
  // src/index.ts
21
163
  var REQUEST_TIMEOUT_MS = 1e4;
22
164
  var CACHE_TTL_MS = 10 * 60 * 1e3;
165
+ var SEARCH_LIMIT = 30;
23
166
  var SEARCH_PAYLOAD_SCHEMA = {
24
167
  type: "object",
25
- properties: { query: { type: "string" } },
168
+ properties: { query: { type: "string", maxLength: 200 } },
26
169
  required: ["query"],
27
170
  additionalProperties: false
28
171
  };
29
172
  var STRING_MAP_SCHEMA = { type: "object", additionalProperties: { type: "string" } };
173
+ var STRING_LIST_SCHEMA = { type: "array", items: { type: "string" } };
30
174
  var TRANSPORT_SCHEMA = {
31
175
  type: "object",
32
176
  required: ["type"],
33
177
  additionalProperties: false,
178
+ oneOf: [
179
+ {
180
+ properties: { type: { const: "stdio" }, command: {}, args: {}, env: {} },
181
+ required: ["type", "command"],
182
+ additionalProperties: false
183
+ },
184
+ {
185
+ properties: { type: { enum: ["http", "sse"] }, url: {}, headers: {} },
186
+ required: ["type", "url"],
187
+ additionalProperties: false
188
+ }
189
+ ],
34
190
  properties: {
35
191
  type: { enum: ["stdio", "http", "sse"] },
36
192
  command: { type: "string" },
37
- args: { type: "array", items: { type: "string" } },
193
+ args: STRING_LIST_SCHEMA,
38
194
  env: STRING_MAP_SCHEMA,
39
195
  url: { type: "string" },
40
196
  headers: STRING_MAP_SCHEMA
41
197
  }
42
198
  };
43
- var SEARCH_RESULT_SCHEMA = {
199
+ var SETUP_SCHEMA = {
44
200
  type: "object",
45
- required: ["status"],
201
+ required: ["kind"],
46
202
  additionalProperties: false,
47
203
  oneOf: [
48
204
  {
49
- properties: { status: { const: "not-configured" } },
50
- required: ["status"],
205
+ properties: { kind: { const: "ready" }, name: {}, transport: {}, needs: {}, optional: {} },
206
+ required: ["kind", "name", "transport", "needs", "optional"],
51
207
  additionalProperties: false
52
208
  },
53
209
  {
54
- properties: { status: { const: "ok" }, items: {}, stale: {} },
55
- required: ["status", "items", "stale"],
210
+ properties: { kind: { const: "manual" } },
211
+ required: ["kind"],
56
212
  additionalProperties: false
57
213
  }
58
214
  ],
59
215
  properties: {
60
- status: { enum: ["not-configured", "ok"] },
216
+ kind: { enum: ["ready", "manual"] },
217
+ name: { type: "string", pattern: "^[a-z0-9][a-z0-9_-]{0,63}$" },
218
+ transport: TRANSPORT_SCHEMA,
219
+ needs: STRING_LIST_SCHEMA,
220
+ optional: STRING_LIST_SCHEMA
221
+ }
222
+ };
223
+ var SEARCH_RESULT_SCHEMA = {
224
+ type: "object",
225
+ required: ["status", "stale", "items"],
226
+ additionalProperties: false,
227
+ properties: {
228
+ status: { const: "ok" },
61
229
  stale: { type: "boolean" },
62
230
  items: {
63
231
  type: "array",
64
232
  items: {
65
233
  type: "object",
66
- required: ["catalogId", "name", "description", "transport", "oauth"],
234
+ required: ["id", "title", "publisher", "description", "version", "setup"],
67
235
  additionalProperties: false,
68
236
  properties: {
69
- catalogId: { type: "string" },
70
- name: { type: "string" },
237
+ id: { type: "string" },
238
+ title: { type: "string" },
239
+ publisher: { type: "string" },
71
240
  description: { type: "string" },
72
- transport: TRANSPORT_SCHEMA,
73
- oauth: { type: "boolean" }
241
+ version: { type: "string" },
242
+ homepage: { type: "string" },
243
+ setup: SETUP_SCHEMA
74
244
  }
75
245
  }
76
246
  }
77
247
  }
78
248
  };
79
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
80
- var isStringRecord = (value) => isRecord(value) && Object.values(value).every((entry) => typeof entry === "string");
81
- var isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
82
- var parseTransport = (value) => {
83
- if (!isRecord(value)) return null;
84
- const type = value["type"];
85
- if (type === "stdio") {
86
- const command = value["command"];
87
- if (typeof command !== "string" || command.length === 0) return null;
88
- const args = value["args"];
89
- const env = value["env"];
90
- if (args !== void 0 && !isStringArray(args)) return null;
91
- if (env !== void 0 && !isStringRecord(env)) return null;
92
- return {
93
- type: "stdio",
94
- command,
95
- ...args === void 0 ? {} : { args },
96
- ...env === void 0 ? {} : { env }
97
- };
98
- }
99
- if (type === "http" || type === "sse") {
100
- const url = value["url"];
101
- if (typeof url !== "string" || url.length === 0) return null;
102
- const headers = value["headers"];
103
- if (headers !== void 0 && !isStringRecord(headers)) return null;
104
- return { type, url, ...headers === void 0 ? {} : { headers } };
105
- }
106
- return null;
107
- };
108
- var parseItem = (value) => {
109
- if (!isRecord(value)) return null;
110
- const catalogId = value["catalogId"];
111
- const name = value["name"];
112
- const description = value["description"];
113
- const oauth = value["oauth"];
114
- const transport = parseTransport(value["transport"]);
115
- if (typeof catalogId !== "string" || catalogId.trim().length === 0 || typeof name !== "string" || name.trim().length === 0 || typeof description !== "string" || typeof oauth !== "boolean" || transport === null) {
116
- return null;
117
- }
118
- return { catalogId, name, description, transport, oauth };
119
- };
120
- var parseRegistryItems = (body) => {
121
- if (!isRecord(body)) return null;
122
- const items = body["items"];
123
- if (!Array.isArray(items)) return null;
124
- const parsed = [];
125
- for (const raw of items) {
126
- const item = parseItem(raw);
127
- if (item === null) return null;
128
- parsed.push(item);
129
- }
130
- return parsed;
131
- };
132
- var readUrl = (config) => {
249
+ var registryBase = (config) => {
133
250
  const value = config["url"];
134
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
251
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : DEFAULT_REGISTRY_URL;
135
252
  };
136
253
  var describeError = (error) => error instanceof Error ? error.message : String(error);
137
- var resolve = (fetched, query) => filterCatalog(mergeCatalog(BUNDLED_CATALOG, fetched), query);
138
- var CATALOG_CACHE_KEY = "catalog";
254
+ var searchCacheKey = (base, query) => `search:${serversEndpoint(base)}:${query.trim().toLowerCase()}`;
139
255
  var search = async (ctx, query) => {
140
- const url = readUrl(ctx.config);
141
- if (url === null) {
142
- return BUNDLED_CATALOG.length === 0 ? { status: "not-configured" } : { status: "ok", items: [...filterCatalog(BUNDLED_CATALOG, query)], stale: false };
143
- }
144
- const cached = await ctx.cache.getJson(CATALOG_CACHE_KEY, CACHE_TTL_MS, true);
145
- const cachedItems = cached === null ? null : parseRegistryItems(cached.value);
256
+ const base = registryBase(ctx.config);
257
+ const key = searchCacheKey(base, query);
258
+ const cached = await ctx.cache.getJson(key, CACHE_TTL_MS, true);
259
+ const cachedItems = cached === null ? null : parseRegistryResponse(cached.value);
146
260
  if (cachedItems !== null && cached !== null && !cached.stale) {
147
- return { status: "ok", items: [...resolve(cachedItems, query)], stale: false };
261
+ return { status: "ok", stale: false, items: cachedItems };
148
262
  }
149
263
  try {
150
- const response = await ctx.http(url, {
264
+ const response = await ctx.http(searchUrl(base, query, SEARCH_LIMIT), {
151
265
  headers: { accept: "application/json" },
152
266
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
153
267
  });
154
268
  if (!response.ok) {
155
- throw new Error(`connector registry responded with HTTP ${response.status}`);
269
+ throw new Error(`MCP registry responded with HTTP ${response.status}`);
156
270
  }
157
- const items = parseRegistryItems(await response.json());
271
+ const body = await response.json();
272
+ const items = parseRegistryResponse(body);
158
273
  if (items === null) {
159
- throw new Error("connector registry response did not match the expected shape");
274
+ throw new Error("MCP registry response did not match the expected shape");
160
275
  }
161
- await ctx.cache.setJson(CATALOG_CACHE_KEY, { items });
162
- return { status: "ok", items: [...resolve(items, query)], stale: false };
276
+ await ctx.cache.setJson(key, body);
277
+ return { status: "ok", stale: false, items };
163
278
  } catch (error) {
164
- const fallback = cachedItems ?? [];
165
- ctx.log.warn("connector registry request failed; serving what is already on hand", {
166
- detail: describeError(error)
167
- });
168
- const items = resolve(fallback, query);
169
- if (items.length > 0 || BUNDLED_CATALOG.length > 0) {
170
- return { status: "ok", items: [...items], stale: true };
279
+ if (cachedItems !== null) {
280
+ ctx.log.warn("MCP registry request failed; serving the cached result", {
281
+ detail: describeError(error)
282
+ });
283
+ return { status: "ok", stale: true, items: cachedItems };
171
284
  }
172
285
  throw error instanceof Error ? error : new Error(describeError(error));
173
286
  }
174
287
  };
288
+ var queryOf = (payload) => typeof payload["query"] === "string" ? payload["query"] : "";
289
+ var AGENT_TOOL_DESCRIPTION = "Search the official MCP registry for MCP servers. Anyone can list a server there and entries are not reviewed, so tell the user who published one before adding it. Pass a short query; an empty query lists the first entries. Each item has the registry id, title, publisher, description, version and an optional homepage. When setup.kind is ready, setup.name and setup.transport describe a server you can add with Ycodium's MCP registration tool after the user agrees. setup.needs names what the user must supply before it works: required environment variables or headers that are empty or still hold a {template}, and the placeholders still in the arguments (<valueHint> or {template}). setup.optional names secrets the server accepts but does not require; offer them, and leave out any the user does not give rather than setting an empty value. Never invent values. When setup.kind is manual the server cannot be added in one step; point the user to its homepage. stale: true means the registry was unreachable and the result is an earlier cached copy.";
175
290
  var connectorRegistryPlugin = definePlugin({
176
291
  id: "connector-registry",
177
- apiVersion: 53,
292
+ apiVersion: 60,
178
293
  activate: (ctx) => {
179
294
  ctx.command({
180
295
  name: "search",
181
- description: "Search the MCP connector catalog configured for this environment.",
296
+ description: "Search the MCP registry configured for this machine.",
182
297
  scope: "read",
183
298
  payloadSchema: SEARCH_PAYLOAD_SCHEMA,
184
299
  resultSchema: SEARCH_RESULT_SCHEMA,
185
- handle: (payload) => {
186
- const query = typeof payload["query"] === "string" ? payload["query"] : "";
187
- return search(ctx, query);
188
- }
300
+ handle: (payload) => search(ctx, queryOf(payload))
301
+ });
302
+ ctx.toolkit({
303
+ name: "connector-registry",
304
+ tools: [
305
+ {
306
+ name: "search",
307
+ description: AGENT_TOOL_DESCRIPTION,
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: {
311
+ query: { type: "string", description: "Words to search for, such as github." }
312
+ }
313
+ },
314
+ handle: async (input) => {
315
+ try {
316
+ return { text: JSON.stringify(await search(ctx, queryOf(input))) };
317
+ } catch (error) {
318
+ return {
319
+ text: `The MCP registry could not be searched: ${describeError(error)}`,
320
+ isError: true
321
+ };
322
+ }
323
+ }
324
+ }
325
+ ]
326
+ });
327
+ ctx.command({
328
+ name: "effectiveSettings",
329
+ description: "Settings card: what the registry search reads when the URL is empty.",
330
+ scope: "read",
331
+ payloadSchema: { type: "object", properties: {}, additionalProperties: false },
332
+ handle: async () => ({ url: { detailKey: "settings.url.official" } })
189
333
  });
190
334
  ctx.settingsCard({
191
335
  titleKey: "settings.card.title",
192
336
  descriptionKey: "settings.card.description",
337
+ effectiveCommand: "effectiveSettings",
193
338
  fields: [
194
339
  {
195
340
  key: "url",
@@ -204,6 +349,5 @@ var connectorRegistryPlugin = definePlugin({
204
349
  });
205
350
  var index_default = connectorRegistryPlugin;
206
351
  export {
207
- CATALOG_CACHE_KEY,
208
352
  index_default as default
209
353
  };
package/locales/en.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
+ "presentation": {
3
+ "name": "Connector catalog",
4
+ "description": "Lets you pick MCP servers from the official MCP registry when adding a server."
5
+ },
2
6
  "settings": {
3
7
  "card": {
4
8
  "title": "Connector registry",
5
- "description": "Where the Catalog tab in Settings → Plugins loads servers from. Defaults to the YCODIUM_MCP_REGISTRY_URL environment variable."
9
+ "description": "Where Add server looks up MCP servers. Defaults to the YCODIUM_MCP_REGISTRY_URL environment variable, then to the official MCP registry."
6
10
  },
7
11
  "url": {
8
12
  "label": "Registry URL",
9
- "description": "Leave empty to fall back to the environment variable, if one is set.",
10
- "placeholder": "https://example.com/connectors/search"
13
+ "description": "Leave empty to use the official MCP registry. Another registry must offer the same /v0/servers interface.",
14
+ "placeholder": "https://registry.modelcontextprotocol.io",
15
+ "official": "Using the official MCP registry"
11
16
  }
12
17
  }
13
18
  }
@@ -1,13 +1,18 @@
1
1
  {
2
+ "presentation": {
3
+ "name": "连接器目录",
4
+ "description": "添加服务器时可以从 MCP 官方清单里挑。"
5
+ },
2
6
  "settings": {
3
7
  "card": {
4
8
  "title": "连接器目录",
5
- "description": "「设置 → 插件 → 连接器」搜索的目录。默认取环境变量 YCODIUM_MCP_REGISTRY_URL。"
9
+ "description": "「添加服务器」从哪里查 MCP 服务器。默认取环境变量 YCODIUM_MCP_REGISTRY_URL,没有则用 MCP 官方清单。"
6
10
  },
7
11
  "url": {
8
- "label": "目录地址",
9
- "description": "留空则回落到环境变量(如果设置了的话)。",
10
- "placeholder": "https://example.com/connectors/search"
12
+ "label": "清单地址",
13
+ "description": "留空用 MCP 官方清单。换成别的清单时,它要提供同样的 /v0/servers 接口。",
14
+ "placeholder": "https://registry.modelcontextprotocol.io",
15
+ "official": "正在用 MCP 官方清单"
11
16
  }
12
17
  }
13
18
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ycodium-ai/plugin-connector-registry",
3
- "version": "0.2.3710",
4
- "description": "Lists MCP servers from the catalog address you set; installing one adds it to this machine's MCP servers.",
3
+ "version": "0.2.3799",
4
+ "description": "Lets you pick MCP servers from the official MCP registry when adding a server.",
5
5
  "keywords": [
6
6
  "connector",
7
7
  "mcp",
@@ -22,12 +22,14 @@
22
22
  "@effect/vitest": "4.0.0-beta.103",
23
23
  "esbuild": "0.28.1",
24
24
  "vite-plus": "0.2.2",
25
- "@ycodium-ai/plugin-api": "0.2.3710"
25
+ "@ycodium-ai/plugin-api": "0.2.3799"
26
26
  },
27
27
  "ycodium": {
28
28
  "plugin": "./dist/index.js",
29
- "displayName": "Connector directory",
30
- "description": "Lists MCP servers from the catalog address you set; installing one adds it to this machine's MCP servers.",
29
+ "displayName": "Connector catalog",
30
+ "displayNameKey": "presentation.name",
31
+ "description": "Lets you pick MCP servers from the official MCP registry when adding a server.",
32
+ "descriptionKey": "presentation.description",
31
33
  "icon": "./assets/icon.svg",
32
34
  "permissions": []
33
35
  },