@chibitek/chibi 0.1.154 → 0.1.155

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.
Files changed (79) hide show
  1. package/dist/assets/{abnfDiagram-O67JEVCF-CbIXKQpc.js → abnfDiagram-O67JEVCF-B8LdsCr1.js} +1 -1
  2. package/dist/assets/{arc-D1xLD8SN.js → arc-DU5fSmKU.js} +1 -1
  3. package/dist/assets/{architectureDiagram-NJMV4G6O-xZe7iJ9y.js → architectureDiagram-NJMV4G6O-DdQSIFKv.js} +1 -1
  4. package/dist/assets/{blockDiagram-BEXU5L5S-Cq35uL03.js → blockDiagram-BEXU5L5S-CaPcGYSS.js} +1 -1
  5. package/dist/assets/{c4Diagram-YGBWAQC7-DjlN_-0r.js → c4Diagram-YGBWAQC7-unt0ePgO.js} +1 -1
  6. package/dist/assets/channel-GBaPOaz-.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-BQpywZWX.js → chunk-2Q5K7J3B-Babg5Fzu.js} +1 -1
  8. package/dist/assets/{chunk-3FUC2YCW-BajOpOBu.js → chunk-3FUC2YCW-D1-dWVHo.js} +2 -2
  9. package/dist/assets/{chunk-5VM5RSS4-DuZpQtDE.js → chunk-5VM5RSS4-DU7nSMbT.js} +1 -1
  10. package/dist/assets/{chunk-GTNCS2PH-Dn8xlUsI.js → chunk-GTNCS2PH-QIjCJIhV.js} +1 -1
  11. package/dist/assets/{chunk-GWA4HPMP-Xrus5lAx.js → chunk-GWA4HPMP-pSByUE5R.js} +1 -1
  12. package/dist/assets/{chunk-JWPE2WC7-Dhf8RCqM.js → chunk-JWPE2WC7-Dj2AaR1b.js} +1 -1
  13. package/dist/assets/{chunk-NETBCI7D-BQXmOQMl.js → chunk-NETBCI7D-pZbN0-FP.js} +1 -1
  14. package/dist/assets/{chunk-WEXAMYUT-BeraxyW-.js → chunk-WEXAMYUT-Bqarwe4D.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-BsKuu9tP.js → chunk-XXDRQBXY-WV8lm3aa.js} +1 -1
  16. package/dist/assets/{classDiagram-v2-NBCMYWYE-CcR0y3zf.js → classDiagram-v2-NBCMYWYE-BANBtky1.js} +1 -1
  17. package/dist/assets/{cose-bilkent-JH36ORCC-Dd2y43ln.js → cose-bilkent-JH36ORCC-CtrktgBC.js} +1 -1
  18. package/dist/assets/{cynefin-OW5HDTMX-DL4Zf255.js → cynefin-OW5HDTMX-DnhVVfba.js} +1 -1
  19. package/dist/assets/{cynefinDiagram-VND7K2PF-CvfZKzI5.js → cynefinDiagram-VND7K2PF-B42SRR6x.js} +1 -1
  20. package/dist/assets/{dagre-6A5THRUB-CZfm2BmL.js → dagre-6A5THRUB-DM--vFMK.js} +1 -1
  21. package/dist/assets/{diagram-22UHCM2B-DEEoufNr.js → diagram-22UHCM2B-ZBupBrPU.js} +1 -1
  22. package/dist/assets/{diagram-3UASUU5V-C1R6O8j1.js → diagram-3UASUU5V-BPQzNIOd.js} +1 -1
  23. package/dist/assets/{diagram-ATOU4E4O-D9dPGToa.js → diagram-ATOU4E4O-5kO8vf8Z.js} +1 -1
  24. package/dist/assets/{diagram-CDSNMT55-BHy3uSmJ.js → diagram-CDSNMT55-DtxogB86.js} +1 -1
  25. package/dist/assets/{diagram-MLGK6HIB-CEo2WbRd.js → diagram-MLGK6HIB-Co9-nYw_.js} +1 -1
  26. package/dist/assets/{diagram-MPIPVDR6-BrZrCrGC.js → diagram-MPIPVDR6-DWRuMh5y.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-ZINNZB2B-CtChmI9r.js → ebnfDiagram-ZINNZB2B-DU_nmoG9.js} +1 -1
  28. package/dist/assets/{elk-276RUBZZ-C-qEle2N.js → elk-276RUBZZ-Bcje7S94.js} +1 -1
  29. package/dist/assets/{erDiagram-OPXOYQCR-BEdc5DT9.js → erDiagram-OPXOYQCR-9aCo_2CI.js} +1 -1
  30. package/dist/assets/{flowDiagram-KWPJA3E3-CT-fOp9_.js → flowDiagram-KWPJA3E3-D4FYFyc5.js} +1 -1
  31. package/dist/assets/{ganttDiagram-FUAMR5RP-LWgFxnGJ.js → ganttDiagram-FUAMR5RP-WQHX5rbx.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-X574FWY7-xHSa3-lE.js → gitGraphDiagram-X574FWY7-DRyzBdiV.js} +1 -1
  33. package/dist/assets/{index-Dy25lFJN.js → index-CG1OvRgO.js} +1 -1
  34. package/dist/assets/{index-BpHS2C5J.js → index-D_8SD_0K.js} +3 -3
  35. package/dist/assets/{infoDiagram-VRGFBTTK-BOyODyhP.js → infoDiagram-VRGFBTTK-D7SOmp4n.js} +1 -1
  36. package/dist/assets/{ishikawaDiagram-OU5B5YK6-D3F47TMU.js → ishikawaDiagram-OU5B5YK6-CdxOnDVX.js} +1 -1
  37. package/dist/assets/{journeyDiagram-ZHPQQLJL-B8h_aacv.js → journeyDiagram-ZHPQQLJL-CH7477Oa.js} +1 -1
  38. package/dist/assets/{kanban-definition-PNTS6WVX-nqNasrdQ.js → kanban-definition-PNTS6WVX-BdpVvkHd.js} +1 -1
  39. package/dist/assets/{linear-CDofN9-p.js → linear-DTGq6qn5.js} +1 -1
  40. package/dist/assets/{mermaid.core-k18eNV07.js → mermaid.core-Sn2sv0Ti.js} +5 -5
  41. package/dist/assets/{mindmap-definition-NLK3R4M7-BYNmNDXA.js → mindmap-definition-NLK3R4M7-koCmkYuq.js} +1 -1
  42. package/dist/assets/{pegDiagram-GJSIUBJH-44c5V9Pa.js → pegDiagram-GJSIUBJH-C3sqhm8e.js} +1 -1
  43. package/dist/assets/{pieDiagram-5QR66LMP-BDvznIYT.js → pieDiagram-5QR66LMP-BELBK122.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-O4NWA36T-BWrzS8SI.js → quadrantDiagram-O4NWA36T-Cksi5LSm.js} +1 -1
  45. package/dist/assets/{railroadDiagram-XR7U4H2S-H_3uxTrq.js → railroadDiagram-XR7U4H2S-f1av_EZW.js} +1 -1
  46. package/dist/assets/{requirementDiagram-PLB6GJNP-BiyKTF3R.js → requirementDiagram-PLB6GJNP-CW4O5nER.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-IPEJSGJF-43RmpN9N.js → sankeyDiagram-IPEJSGJF-Dz8ADHNY.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-PO4LG4MO-B0OUYqhV.js → sequenceDiagram-PO4LG4MO-Cc3HE0iZ.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-xiXiEFtk.js → sizeCapture-INFHLROL-gdaZNHnK.js} +1 -1
  50. package/dist/assets/{stateDiagram-v2-GCMORJYK-C4-l5TZJ.js → stateDiagram-v2-GCMORJYK--u8LCnab.js} +1 -1
  51. package/dist/assets/{swimlanes-2SLR337P-B58p5Kvz.js → swimlanes-2SLR337P-4MAw4kdO.js} +1 -1
  52. package/dist/assets/swimlanesDiagram-TC7HE7FX-CamSa0NE.js +8 -0
  53. package/dist/assets/{timeline-definition-EJHVYXUP-DUgR2Qsq.js → timeline-definition-EJHVYXUP-CL89YkCT.js} +1 -1
  54. package/dist/assets/{usecaseDiagram-POWQR4AR-DcicVGBU.js → usecaseDiagram-POWQR4AR-C92l7YQe.js} +1 -1
  55. package/dist/assets/{vennDiagram-UO4OBE2U-9pThMhQ1.js → vennDiagram-UO4OBE2U-17siYhSj.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-VNRHLVJA-CStNs1Wv.js → wardleyDiagram-VNRHLVJA-CNwa9VmN.js} +1 -1
  57. package/dist/assets/{xychartDiagram-PMCCYNJV-JmQMXi-y.js → xychartDiagram-PMCCYNJV-Dnu6Xzih.js} +1 -1
  58. package/dist/index.html +1 -1
  59. package/dist-server/connector-proxy.js +205 -0
  60. package/dist-server/index.js +93 -38
  61. package/dist-server/mcp-gate.js +7 -4
  62. package/dist-server/openmausbot.js +16 -22
  63. package/dist-server/pair-cli.js +16 -22
  64. package/dist-server/server/composio.js +1051 -0
  65. package/dist-server/server/connector-proxy.js +249 -0
  66. package/dist-server/server/drivers/codex.js +18 -23
  67. package/dist-server/server/incidents.js +8 -4
  68. package/dist-server/server/index.js +14 -16
  69. package/dist-server/server/mcp-auth-signal.js +2 -1
  70. package/dist-server/server/mcp-gate-config.js +1 -0
  71. package/dist-server/server/mcp-gate.js +7 -4
  72. package/dist-server/server/mcp-health.js +14 -4
  73. package/dist-server/server/mcp-public-registry.js +325 -0
  74. package/dist-server/server/mcp-turn.js +26 -0
  75. package/dist-server/server/system-prompt.js +14 -6
  76. package/dist-server/shared/connector-availability.js +12 -0
  77. package/package.json +1 -1
  78. package/dist/assets/channel-Bm-MISs3.js +0 -1
  79. package/dist/assets/swimlanesDiagram-TC7HE7FX-CPlL77A3.js +0 -8
@@ -0,0 +1,325 @@
1
+ // The Plugins window's MCP Marketplace, backed by the official MCP Registry
2
+ // (https://registry.modelcontextprotocol.io, public, no key). This module
3
+ // fetches and normalizes registry pages and turns one entry, or a featured
4
+ // service from shared/mcp-featured.ts, into a config.json `mcpServers` entry.
5
+ // Adding goes through the same rules as every other MCP server: the entry is
6
+ // saved switched off, secrets are kept in its env or headers like any other
7
+ // server's, and nothing here logs a value.
8
+ import { featuredMcpIcon, featuredMcpServer, MCP_FEATURED } from "../shared/mcp-featured.js";
9
+ import { slugMcpName } from "./mcp-registry.js";
10
+ export const MCP_REGISTRY_BASE = "https://registry.modelcontextprotocol.io";
11
+ /** The OAuth bridge Atomic already runs remote servers through (see
12
+ * electron/chibitek-defaults.mjs MCP_REMOTE_PACKAGE; a test keeps them equal).
13
+ * Pinned so its saved sign-ins survive a new mcp-remote release. */
14
+ export const MCP_REMOTE_PACKAGE = "mcp-remote@0.14.3";
15
+ const CACHE_MS = 60 * 60 * 1000;
16
+ const CACHE_MAX = 200;
17
+ const TIMEOUT_MS = 8_000;
18
+ const PAGE_SIZE = 30;
19
+ const OFFICIAL = "io.modelcontextprotocol.registry/official";
20
+ export const REGISTRY_OFFLINE_MESSAGE = "The MCP Registry could not be reached. Check your internet connection and try again.";
21
+ export class RegistryUnavailableError extends Error {
22
+ constructor() {
23
+ super(REGISTRY_OFFLINE_MESSAGE);
24
+ this.name = "RegistryUnavailableError";
25
+ }
26
+ }
27
+ function record(value) {
28
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
29
+ }
30
+ function text(value) {
31
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
32
+ }
33
+ function httpsUrl(value) {
34
+ const raw = text(value);
35
+ if (!raw)
36
+ return undefined;
37
+ try {
38
+ const url = new URL(raw);
39
+ return url.protocol === "https:" ? url.toString() : undefined;
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }
45
+ const featuredByRegistryName = new Map(MCP_FEATURED.filter((server) => server.registryName).map((server) => [server.registryName, server]));
46
+ /** "io.github.acme/thing" -> "acme", "com.notion/mcp" -> "notion.com". */
47
+ export function publisherFor(name) {
48
+ const namespace = name.split("/")[0] ?? name;
49
+ if (namespace.startsWith("io.github."))
50
+ return namespace.slice("io.github.".length);
51
+ return namespace.split(".").reverse().join(".");
52
+ }
53
+ /** "notion-mcp-server" -> "Notion Mcp Server" when the entry has no title. */
54
+ function titleFor(name) {
55
+ const leaf = name.split("/").pop() ?? name;
56
+ return leaf.split(/[-_.]+/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join(" ") || name;
57
+ }
58
+ function githubOwner(repositoryUrl) {
59
+ if (!repositoryUrl)
60
+ return undefined;
61
+ const match = /^https:\/\/github\.com\/([A-Za-z0-9-]{1,39})(?:\/|$)/.exec(repositoryUrl);
62
+ return match?.[1];
63
+ }
64
+ function input(kind, raw) {
65
+ const item = record(raw);
66
+ const name = text(item.name);
67
+ if (!name)
68
+ return null;
69
+ const description = text(item.description);
70
+ return {
71
+ kind,
72
+ name,
73
+ ...(description ? { description: description.slice(0, 300) } : {}),
74
+ required: item.isRequired === true,
75
+ secret: item.isSecret === true,
76
+ ...(kind === "header" && text(item.value) ? { template: text(item.value) } : {}),
77
+ };
78
+ }
79
+ function remotes(raw) {
80
+ if (!Array.isArray(raw))
81
+ return [];
82
+ return raw.flatMap((entry) => {
83
+ const item = record(entry);
84
+ const url = httpsUrl(item.url);
85
+ // A URL with {placeholders} needs per-person values Atomic cannot guess.
86
+ if (!url || /[{}]|%7B|%7D/i.test(url))
87
+ return [];
88
+ const type = item.type === "sse" ? "sse" : item.type === "streamable-http" ? "http" : null;
89
+ if (!type)
90
+ return [];
91
+ const headers = Array.isArray(item.headers) ? item.headers.map((header) => input("header", header)).filter((header) => header !== null) : [];
92
+ return [{ type, url, headers }];
93
+ });
94
+ }
95
+ function packages(raw) {
96
+ if (!Array.isArray(raw))
97
+ return [];
98
+ return raw.flatMap((entry) => {
99
+ const item = record(entry);
100
+ const registryType = text(item.registryType);
101
+ const identifier = text(item.identifier);
102
+ if (!registryType || !identifier)
103
+ return [];
104
+ const env = Array.isArray(item.environmentVariables)
105
+ ? item.environmentVariables.map((variable) => input("env", variable)).filter((variable) => variable !== null)
106
+ : [];
107
+ return [{
108
+ registryType,
109
+ identifier,
110
+ ...(text(item.version) ? { version: text(item.version) } : {}),
111
+ ...(text(record(item.transport).type) ? { transport: text(record(item.transport).type) } : {}),
112
+ ...(text(item.runtimeHint) ? { runtimeHint: text(item.runtimeHint) } : {}),
113
+ env,
114
+ }];
115
+ });
116
+ }
117
+ const SAFE_PACKAGE = /^(@[a-z0-9][\w.-]*\/)?[A-Za-z0-9][\w.-]*$/;
118
+ const SAFE_VERSION = /^[\w.+-]{1,64}$/;
119
+ /** Remote first (nothing to install), then npm, then PyPI. A remote whose
120
+ * headers are all optional is assumed to sign in with OAuth, so it runs
121
+ * through the mcp-remote bridge; one that requires a header (an API key) is
122
+ * kept as a plain URL entry with that header. */
123
+ function installPlan(server) {
124
+ const remote = server.remotes.find((item) => item.type === "http") ?? server.remotes.find((item) => item.type === "sse");
125
+ if (remote)
126
+ return { kind: "remote", remote, oauth: !remote.headers.some((header) => header.required) };
127
+ const stdio = (item) => !item.transport || item.transport === "stdio";
128
+ const npm = server.packages.find((item) => item.registryType === "npm" && stdio(item) && SAFE_PACKAGE.test(item.identifier));
129
+ if (npm)
130
+ return { kind: "npm", pkg: npm };
131
+ const pypi = server.packages.find((item) => item.registryType === "pypi" && stdio(item) && SAFE_PACKAGE.test(item.identifier));
132
+ if (pypi)
133
+ return { kind: "pypi", pkg: pypi };
134
+ return null;
135
+ }
136
+ function planInputs(plan) {
137
+ if (!plan)
138
+ return [];
139
+ const list = plan.kind === "remote"
140
+ ? plan.oauth ? [] : plan.remote.headers.map(({ template: _template, ...header }) => header)
141
+ : plan.pkg.env;
142
+ return [...list].sort((a, b) => Number(b.required) - Number(a.required));
143
+ }
144
+ /** One registry list entry to the shape the renderer shows, or null when it
145
+ * is not the latest version or not usable at all. */
146
+ export function normalizeRegistryEntry(raw) {
147
+ const entry = record(raw);
148
+ const server = record(entry.server);
149
+ const meta = record(record(entry._meta)[OFFICIAL]);
150
+ if (meta.isLatest === false)
151
+ return null;
152
+ if (typeof meta.status === "string" && meta.status !== "active")
153
+ return null;
154
+ const name = text(server.name);
155
+ if (!name)
156
+ return null;
157
+ const repositoryUrl = httpsUrl(record(server.repository).url);
158
+ const websiteUrl = httpsUrl(server.websiteUrl);
159
+ const featured = featuredByRegistryName.get(name);
160
+ const icons = Array.isArray(server.icons) ? server.icons : [];
161
+ const registryIcon = icons.map((icon) => httpsUrl(record(icon).src)).find(Boolean);
162
+ const owner = githubOwner(repositoryUrl);
163
+ const iconUrl = featured
164
+ ? featuredMcpIcon(featured.id)
165
+ : registryIcon ?? (owner ? `https://github.com/${owner}.png?size=64` : undefined);
166
+ const normalized = {
167
+ id: name,
168
+ name,
169
+ title: text(server.title) ?? featured?.name ?? titleFor(name),
170
+ description: (text(server.description) ?? "").slice(0, 400),
171
+ publisher: publisherFor(name),
172
+ version: text(server.version) ?? "",
173
+ ...(repositoryUrl ? { repositoryUrl } : {}),
174
+ ...(websiteUrl ? { websiteUrl } : {}),
175
+ remotes: remotes(server.remotes),
176
+ packages: packages(server.packages),
177
+ ...(iconUrl ? { iconUrl } : {}),
178
+ ...(featured ? { featured: featured.id } : {}),
179
+ };
180
+ const plan = installPlan(normalized);
181
+ return { ...normalized, install: plan?.kind ?? null, inputs: planInputs(plan) };
182
+ }
183
+ /** Normalize a page: latest versions only, one card per server name. */
184
+ export function normalizeRegistryPage(body) {
185
+ const page = record(body);
186
+ const seen = new Set();
187
+ const servers = [];
188
+ for (const raw of Array.isArray(page.servers) ? page.servers : []) {
189
+ const server = normalizeRegistryEntry(raw);
190
+ if (!server || seen.has(server.id))
191
+ continue;
192
+ seen.add(server.id);
193
+ servers.push(server);
194
+ }
195
+ const nextCursor = text(record(page.metadata).nextCursor);
196
+ return { servers, ...(nextCursor ? { nextCursor } : {}) };
197
+ }
198
+ /** A registry client with a one-hour in-memory cache. Every failure (offline,
199
+ * timeout, a registry error) becomes RegistryUnavailableError. */
200
+ export function createMcpPublicRegistry(options = {}) {
201
+ const doFetch = options.fetch ?? fetch;
202
+ const base = options.base ?? MCP_REGISTRY_BASE;
203
+ const now = options.now ?? Date.now;
204
+ const cache = new Map();
205
+ async function getJson(url) {
206
+ const hit = cache.get(url);
207
+ if (hit && now() - hit.at < CACHE_MS)
208
+ return hit.value;
209
+ let response;
210
+ try {
211
+ response = await doFetch(url, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(TIMEOUT_MS) });
212
+ }
213
+ catch {
214
+ throw new RegistryUnavailableError();
215
+ }
216
+ if (response.status === 404)
217
+ return null;
218
+ if (!response.ok)
219
+ throw new RegistryUnavailableError();
220
+ let value;
221
+ try {
222
+ value = await response.json();
223
+ }
224
+ catch {
225
+ throw new RegistryUnavailableError();
226
+ }
227
+ if (cache.size >= CACHE_MAX)
228
+ cache.delete(cache.keys().next().value);
229
+ cache.set(url, { at: now(), value });
230
+ return value;
231
+ }
232
+ return {
233
+ async search({ search, cursor }) {
234
+ const params = new URLSearchParams({ limit: String(PAGE_SIZE), version: "latest" });
235
+ const query = search?.trim().slice(0, 100);
236
+ if (query)
237
+ params.set("search", query);
238
+ if (cursor)
239
+ params.set("cursor", cursor.slice(0, 300));
240
+ return normalizeRegistryPage(await getJson(`${base}/v0/servers?${params}`));
241
+ },
242
+ async get(id) {
243
+ const body = await getJson(`${base}/v0/servers/${encodeURIComponent(id)}/versions/latest`);
244
+ return body === null ? null : normalizeRegistryEntry(body);
245
+ },
246
+ };
247
+ }
248
+ /** A free config name from the server's own name: "notion", then "notion-2". */
249
+ export function freeMcpName(preferred, taken) {
250
+ const base = slugMcpName(preferred) || "mcp-server";
251
+ if (!taken(base))
252
+ return base;
253
+ for (let n = 2; n < 100; n += 1) {
254
+ const candidate = `${base.slice(0, 32 - String(n).length - 1)}-${n}`;
255
+ if (!taken(candidate))
256
+ return candidate;
257
+ }
258
+ return `${base.slice(0, 23)}-${Date.now().toString(36).slice(-8)}`;
259
+ }
260
+ function bridgeEntry(url) {
261
+ return { command: "npx", args: ["-y", MCP_REMOTE_PACKAGE, url], env: {} };
262
+ }
263
+ function cleanValues(values) {
264
+ const out = {};
265
+ for (const [key, value] of Object.entries(record(values))) {
266
+ if (typeof value === "string" && value.trim())
267
+ out[key] = value.trim();
268
+ }
269
+ return out;
270
+ }
271
+ /** The config entry a featured service becomes: its official URL through the
272
+ * OAuth bridge. */
273
+ export function planFeaturedAdd(featured, taken) {
274
+ return { ok: true, name: freeMcpName(featured.id, taken), entry: bridgeEntry(featured.url), kind: "remote", oauth: true };
275
+ }
276
+ /** The config entry a registry server becomes, with the person's values for
277
+ * its inputs. Required inputs that are missing are refused with the list, so
278
+ * the renderer can ask for them. */
279
+ export function planRegistryAdd(server, rawValues, taken) {
280
+ const values = cleanValues(rawValues);
281
+ const plan = installPlan(server);
282
+ if (!plan) {
283
+ const docker = server.packages.some((item) => item.registryType === "oci" || item.registryType === "docker");
284
+ return {
285
+ ok: false,
286
+ status: 400,
287
+ code: "unsupported",
288
+ error: docker
289
+ ? `${server.title} only ships as a Docker image, which Atomic can't add for you yet. Add it by hand under MCP servers.`
290
+ : `${server.title} doesn't offer a remote address or an npm or PyPI package, so Atomic can't add it for you. Add it by hand under MCP servers.`,
291
+ };
292
+ }
293
+ const inputs = planInputs(plan);
294
+ const missing = inputs.filter((item) => item.required && !values[item.name]);
295
+ if (missing.length) {
296
+ return { ok: false, status: 400, code: "needs_input", inputs, error: `${server.title} needs ${missing.map((item) => item.name).join(", ")} before it can be added.` };
297
+ }
298
+ const name = freeMcpName(server.featured ?? server.name.split("/").pop() ?? server.title, taken);
299
+ if (plan.kind === "remote") {
300
+ if (plan.oauth)
301
+ return { ok: true, name, entry: bridgeEntry(plan.remote.url), kind: "remote", oauth: true };
302
+ const headers = {};
303
+ for (const header of plan.remote.headers) {
304
+ const value = values[header.name];
305
+ if (!value)
306
+ continue;
307
+ // "Bearer {api_key}" -> "Bearer <value>" unless the value already is the whole header.
308
+ headers[header.name] = header.template && /\{[^}]+\}/.test(header.template) && !/\s/.test(value)
309
+ ? header.template.replace(/\{[^}]+\}/, value)
310
+ : value;
311
+ }
312
+ return { ok: true, name, entry: { type: plan.remote.type, url: plan.remote.url, headers }, kind: "remote", oauth: false };
313
+ }
314
+ const env = {};
315
+ for (const variable of plan.pkg.env) {
316
+ if (values[variable.name])
317
+ env[variable.name] = values[variable.name];
318
+ }
319
+ const version = plan.pkg.version && SAFE_VERSION.test(plan.pkg.version) ? plan.pkg.version : undefined;
320
+ if (plan.kind === "npm") {
321
+ return { ok: true, name, entry: { command: "npx", args: ["-y", version ? `${plan.pkg.identifier}@${version}` : plan.pkg.identifier], env }, kind: "npm", oauth: false };
322
+ }
323
+ return { ok: true, name, entry: { command: "uvx", args: [plan.pkg.identifier], env }, kind: "pypi", oauth: false };
324
+ }
325
+ export { featuredMcpServer };
@@ -0,0 +1,26 @@
1
+ import { drainMcpAuthSignals } from "./mcp-auth-signal.js";
2
+ export function mcpTurnPolicy(deps) {
3
+ return {
4
+ /** Fold in what gates and the chat runtime reported since the last look. */
5
+ sync() {
6
+ const signals = drainMcpAuthSignals(deps.signalDir());
7
+ if (!signals.length)
8
+ return;
9
+ const specs = deps.specs();
10
+ deps.auth.ingest(signals, specs);
11
+ for (const signal of deps.health.ingest(signals, specs)) {
12
+ if (signal.threadId)
13
+ deps.reportUnavailable(signal.threadId, `the "${signal.name}" MCP server ${signal.kind === "crashed" ? "stopped during the turn" : "would not start"}; the turn ran on without its tools`);
14
+ }
15
+ },
16
+ /** The servers one turn starts: `servers` minus any whose sign-in is
17
+ * backing off or stopped, or that lately would not start or crashed. */
18
+ mountable(servers) {
19
+ return Object.fromEntries(Object.entries(servers).filter(([name, spec]) => deps.auth.mountable(name, spec) && deps.health.mountable(name, spec)));
20
+ },
21
+ /** The ones left out for being broken, so the model is told in one line. */
22
+ unavailable(servers) {
23
+ return Object.entries(servers).flatMap(([name, spec]) => { const kind = deps.health.unavailableKind(name, spec); return kind ? [{ name, kind }] : []; });
24
+ },
25
+ };
26
+ }
@@ -99,12 +99,20 @@ export function accountsPrompt(emails) {
99
99
  return ` You can use the user's ${emails.length === 1 ? "account" : "accounts"} ${list} through atomic_accounts (mail, calendar and files). Use list_accounts to check granted permissions. Do not use LLM account connectors for Google or Microsoft services. Pass the email as \`account\` when more than one account of that provider is listed. Save messages as drafts and send only when the user asked you to send.`;
100
100
  }
101
101
  /** Names the user-added MCP servers a turn actually mounted, so the bot
102
- * reaches for them instead of saying it has no such tool. Empty when none. */
103
- export function customMcpPrompt(names) {
104
- if (names.length === 0)
105
- return "";
106
- const list = names.map((name) => `"${name}"`).join(", ");
107
- return ` The user also added ${names.length === 1 ? "an MCP server" : "MCP servers"} for you: ${list}. Use their available tools under the engine's normal approval rules.`;
102
+ * reaches for them instead of saying it has no such tool, and the ones left
103
+ * out because they are broken, in one plain line each with no error text,
104
+ * so the bot carries on instead of hunting for a tool that is not there.
105
+ * Empty when there are neither. */
106
+ export function customMcpPrompt(names, unavailable = []) {
107
+ const parts = [];
108
+ if (names.length > 0) {
109
+ const list = names.map((name) => `"${name}"`).join(", ");
110
+ parts.push(` The user also added ${names.length === 1 ? "an MCP server" : "MCP servers"} for you: ${list}. Use their available tools under the engine's normal approval rules.`);
111
+ }
112
+ for (const server of unavailable) {
113
+ parts.push(` The "${server.name}" tool server ${server.kind === "crashed" ? "stopped working during a recent turn" : "would not start"}, so its tools are left out of this turn; Atomic tries it again later. Carry on without them.`);
114
+ }
115
+ return parts.join("");
108
116
  }
109
117
  export const CREDENTIAL_PROMPT = " If a supported API key is missing for the service actually needed, use request_credential to create a secure credential request. Before requesting a computer-provider key, inspect the configured targets with select_computer; an existing self-hosted VPS does not need Box credentials. Do not request a different provider's key merely because a task mentions cloud. A freshly QR-paired mobile app or the desktop app can show the secure entry card. Never claim it opened unless the request succeeded, and never ask the user to paste credentials into chat.";
110
118
  export const THREADS_PROMPT = " A thread is one conversation with its own history and its own run; a bot can have several running at once, and the person sees them as rows under that bot. Use start_thread to open one on yourself for separate work, or on a teammate to hand them a job that should run on its own. Use list_threads to see how the ones you opened are going. When you mention a thread to the person, write its title as #Title so it links. Do not use a ticket comment, a note, or a room post as a stand-in for a thread.";
@@ -0,0 +1,12 @@
1
+ const MANAGED_CUSTOM_AUTH_TOOLKITS = new Set(["twitter", "x"]);
2
+ /**
3
+ * Some Composio toolkits no longer include provider-managed credentials.
4
+ * The official OpenMausBot broker cannot offer those connections until its
5
+ * project owns the corresponding OAuth app. Self-hosted Composio projects can
6
+ * still supply their own auth config, so keep this restriction mode-specific.
7
+ */
8
+ export function managedConnectorUnavailableReason(mode, slug) {
9
+ if (mode !== "managed" || !MANAGED_CUSTOM_AUTH_TOOLKITS.has(slug.trim().toLowerCase()))
10
+ return null;
11
+ return "Twitter/X needs your own X Developer app and Composio auth config. Use self-hosted connected apps for now.";
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chibitek/chibi",
3
- "version": "0.1.154",
3
+ "version": "0.1.155",
4
4
  "description": "Atomic by Chibitek: run your AI team's server anywhere and pair your devices to it",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- import{U as a,C as n}from"./mermaid.core-k18eNV07.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-KWPJA3E3-CT-fOp9_.js";import{_ as a}from"./mermaid.core-k18eNV07.js";import"./chunk-5VM5RSS4-DuZpQtDE.js";import"./chunk-XXDRQBXY-BsKuu9tP.js";import"./chunk-WEXAMYUT-BeraxyW-.js";import"./chunk-GWA4HPMP-Xrus5lAx.js";import"./channel-Bm-MISs3.js";import"./index-BpHS2C5J.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster:not([data-color-id]) rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),s=o,u=r({styles:s});export{u as diagram};