@zibby/mcp-cli 0.2.1 → 0.3.2
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 +31 -26
- package/index.js +232 -308
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
# @zibby/mcp-cli
|
|
2
2
|
|
|
3
|
-
Zibby's CLI as a Model Context Protocol server. Lets any MCP-aware AI agent — Claude Code, Cursor, OpenAI Codex, Gemini CLI, Continue, Cline, Aider, Goose —
|
|
3
|
+
Zibby's **local-essential** CLI as a Model Context Protocol server. Lets any MCP-aware AI agent — Claude Code, Cursor, OpenAI Codex, Gemini CLI, Continue, Cline, Aider, Goose — log in to Zibby, scaffold/validate/run workflows on local disk, and deploy or download workflows from a chat session.
|
|
4
|
+
|
|
5
|
+
> **Looking for `list_projects`, `trigger_workflow`, `workflow_logs`, marketplace apps, ticket/integration browsing, etc.?** Those tools moved to the **Zibby Remote MCP** at `https://api-prod.zibby.app/mcp` (HTTP transport, 71 tools, no install). Hook your agent up to both — this stdio package for the things that touch your filesystem, the remote MCP for everything else. The remote MCP picks up server-side changes without you having to re-publish or upgrade npm.
|
|
4
6
|
|
|
5
7
|
## What you get
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
9 MCP tools, every one of them genuinely local:
|
|
8
10
|
|
|
9
|
-
| Tool |
|
|
11
|
+
| Tool | Why it has to be local |
|
|
10
12
|
|---|---|
|
|
11
|
-
| `zibby_login` | Opens the user's browser
|
|
12
|
-
| `zibby_logout` | Clears the saved session. |
|
|
13
|
-
| `zibby_status` |
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| `zibby_run_workflow_local` | Runs a workflow locally one-shot for debugging — no cloud. |
|
|
23
|
-
| `zibby_download_workflow` | Downloads a deployed workflow back to local. Requires explicit user confirmation. |
|
|
24
|
-
|
|
25
|
-
Destructive ops (`workflow delete`, `env set/unset`, `schedule set/clear`, `creds`) are **intentionally not exposed**. Manage those from the `zibby` CLI directly.
|
|
13
|
+
| `zibby_login` | Opens the user's browser (device-code OAuth) and writes the session token + cached project API tokens to `~/.zibby/config.json`. |
|
|
14
|
+
| `zibby_logout` | Clears the saved session from `~/.zibby/config.json`. |
|
|
15
|
+
| `zibby_status` | Reads `~/.zibby/config.json` to show which login is active on **this machine**. (The remote MCP also has a `zibby_status`, but only this one sees the local file.) |
|
|
16
|
+
| `zibby_list_templates` | Lists workflow templates bundled with the local `@zibby/cli` — no network. |
|
|
17
|
+
| `zibby_scaffold_workflow` | Writes `.zibby/workflows/<name>/` files to local disk. |
|
|
18
|
+
| `zibby_validate_workflow` | Reads local workflow files + runs the validator (~30ms, no API). |
|
|
19
|
+
| `zibby_run_workflow_local` | Spawns a local node process to run a workflow against local data. |
|
|
20
|
+
| `zibby_deploy_workflow` | Bundles the local workflow folder + dependencies, then uploads. Bundling is local. |
|
|
21
|
+
| `zibby_download_workflow` | Downloads a deployed workflow back to local disk. Requires explicit user confirmation. |
|
|
22
|
+
|
|
23
|
+
Destructive ops (`workflow delete`, `env set/unset`, `schedule set/clear`, `creds`) are **intentionally not exposed**. Manage those from the `zibby` CLI directly, or via the Zibby Remote MCP where they exist.
|
|
26
24
|
|
|
27
25
|
## Prerequisites
|
|
28
26
|
|
|
@@ -43,6 +41,10 @@ That's it. No global `npm install` needed — `npx` handles the bundle (which in
|
|
|
43
41
|
"zibby": {
|
|
44
42
|
"command": "npx",
|
|
45
43
|
"args": ["-y", "@zibby/mcp-cli"]
|
|
44
|
+
},
|
|
45
|
+
"zibby-remote": {
|
|
46
|
+
"type": "http",
|
|
47
|
+
"url": "https://api-prod.zibby.app/mcp"
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -125,7 +127,9 @@ If your agent on Windows can't find `npx`, wrap it in `cmd /c`:
|
|
|
125
127
|
Two-stage by design (mirrors how the `zibby` CLI works):
|
|
126
128
|
|
|
127
129
|
1. **Session token** (`zibby_login`) — device-code OAuth via browser. Identifies the user.
|
|
128
|
-
2. **Per-project API tokens** — fetched by `zibby_login`
|
|
130
|
+
2. **Per-project API tokens** — fetched by `zibby_login` and cached locally in `~/.zibby/config.json`. The MCP server picks the right token automatically when you call a project-scoped tool like `zibby_deploy_workflow` or `zibby_download_workflow`.
|
|
131
|
+
|
|
132
|
+
If you grant your account access to a new project, re-run `zibby_login` (or `zibby_logout` + `zibby_login`) to refresh the cached project list.
|
|
129
133
|
|
|
130
134
|
All credentials live in `~/.zibby/config.json` (mode `0600`). The MCP server reads/writes that file directly — no separate credential store.
|
|
131
135
|
|
|
@@ -135,16 +139,16 @@ The user's password never touches the MCP server: login is OAuth in the browser,
|
|
|
135
139
|
|
|
136
140
|
```
|
|
137
141
|
User: Deploy the browser-test template to my "playhouse" project.
|
|
138
|
-
Agent: → zibby_list_projects
|
|
139
|
-
→ zibby_scaffold_workflow
|
|
140
|
-
→ zibby_validate_workflow
|
|
141
|
-
→ zibby_deploy_workflow
|
|
142
|
+
Agent: → (via Remote MCP) zibby_list_projects (finds projectId)
|
|
143
|
+
→ zibby_scaffold_workflow (browser-test-automation → .zibby/workflows/playhouse-tests/)
|
|
144
|
+
→ zibby_validate_workflow (catches obvious errors locally)
|
|
145
|
+
→ zibby_deploy_workflow (bundles local folder, uploads, returns UUID + version)
|
|
142
146
|
→ "Deployed v1 of playhouse-tests. UUID 988…"
|
|
143
147
|
|
|
144
148
|
User: Run it against staging.zibby.dev.
|
|
145
|
-
Agent: → zibby_trigger_workflow
|
|
149
|
+
Agent: → (via Remote MCP) zibby_trigger_workflow (input: { url: "https://staging.zibby.dev" })
|
|
146
150
|
→ returns { jobId: "abc-123" }
|
|
147
|
-
→
|
|
151
|
+
→ (via Remote MCP) zibby_get_workflow_job_logs (jobId: "abc-123")
|
|
148
152
|
→ "Run completed. Found 0 errors."
|
|
149
153
|
```
|
|
150
154
|
|
|
@@ -153,10 +157,11 @@ Agent: → zibby_trigger_workflow (input: { url: "https://staging.zibby
|
|
|
153
157
|
| Problem | Likely cause |
|
|
154
158
|
|---|---|
|
|
155
159
|
| `Not logged in` on every call | `~/.zibby/config.json` missing or corrupted. Call `zibby_login`. |
|
|
156
|
-
| `No API token cached for project` | Project list out of date.
|
|
160
|
+
| `No API token cached for project` | Project list out of date. Re-run `zibby_login` (or `zibby_logout` + `zibby_login`) to refresh. |
|
|
157
161
|
| Tool returns text with ANSI color codes | Some agent UIs don't strip them. We set `NO_COLOR=1` already; if you still see them, your agent's display is the issue. |
|
|
158
162
|
| `npx -y` hangs on first install | First-time download. Subsequent invocations are cached. |
|
|
159
163
|
| Tool times out on long deploys | The wrapped CLI command exceeded 10 min. Re-run from a terminal with `zibby workflow deploy` to see live output. |
|
|
164
|
+
| Wanting `list_projects` / `trigger_workflow` / `workflow_logs` | Those live in the Zibby Remote MCP. Add it as a second `mcpServers` entry (`https://api-prod.zibby.app/mcp`). |
|
|
160
165
|
|
|
161
166
|
## Security model
|
|
162
167
|
|
package/index.js
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* global process */
|
|
3
3
|
/**
|
|
4
|
-
* Zibby CLI MCP Server
|
|
4
|
+
* Zibby CLI MCP Server (local-essential tools only)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* This stdio MCP server exposes the @zibby/cli surface that genuinely needs
|
|
7
|
+
* to run on the user's local machine — login (OAuth browser flow), local
|
|
8
|
+
* workflow scaffolding/validate/run, deploy (bundles local files), and
|
|
9
|
+
* download (writes files locally).
|
|
10
|
+
*
|
|
11
|
+
* Every pure-API tool (list projects, list/trigger workflows, fetch logs,
|
|
12
|
+
* marketplace apps, etc.) is served by the Zibby Remote MCP at
|
|
13
|
+
* https://api-prod.zibby.app/mcp — point your agent there for those. The
|
|
14
|
+
* remote MCP picks up server-side changes without an npm publish/upgrade,
|
|
15
|
+
* so we deliberately do NOT duplicate that surface here.
|
|
9
16
|
*
|
|
10
17
|
* Distribution model: stdio MCP server published to npm. Spawned by the
|
|
11
18
|
* agent's host process via `npx -y @zibby/mcp-cli`. The agent's stdin/stdout
|
|
12
|
-
* is the MCP transport. All HTTP
|
|
13
|
-
*
|
|
14
|
-
* (the same file `zibby login` writes).
|
|
19
|
+
* is the MCP transport. All HTTP traffic for the OAuth flow goes
|
|
20
|
+
* user-machine → api-prod.zibby.app. Project-scoped API tokens are cached
|
|
21
|
+
* in ~/.zibby/config.json (the same file `zibby login` writes).
|
|
15
22
|
*
|
|
16
|
-
* Implementation: shell-out to the bundled @zibby/cli binary
|
|
17
|
-
*
|
|
18
|
-
* (independent of any global
|
|
23
|
+
* Implementation: shell-out to the bundled @zibby/cli binary for the local
|
|
24
|
+
* file-touching commands. We resolve the CLI through our own node_modules
|
|
25
|
+
* so the version is pinned via package.json (independent of any global
|
|
26
|
+
* PATH install).
|
|
19
27
|
*
|
|
20
28
|
* Auth: login is implemented in-process via the device-code OAuth flow
|
|
21
29
|
* against api-prod.zibby.app/cli/login/{initiate,poll}. We mirror the file
|
|
@@ -38,10 +46,19 @@ const require = createRequire(import.meta.url);
|
|
|
38
46
|
|
|
39
47
|
// ── Constants ───────────────────────────────────────────────────────────
|
|
40
48
|
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
49
|
+
// Resolve @zibby/cli's bin path via ITS package.json "bin" field (not a
|
|
50
|
+
// hard-coded subpath). Dev workspace has bin/zibby.js at the top level;
|
|
51
|
+
// the npm-published package ships dist/bin/zibby.js — so the literal
|
|
52
|
+
// `@zibby/cli/bin/zibby.js` subpath only works in dev and throws
|
|
53
|
+
// MODULE_NOT_FOUND under npx. Reading package.json + dirname + bin entry
|
|
54
|
+
// works in both layouts.
|
|
55
|
+
const ZIBBY_BIN = (() => {
|
|
56
|
+
const pkgPath = require.resolve('@zibby/cli/package.json');
|
|
57
|
+
const pkg = require(pkgPath);
|
|
58
|
+
const binEntry = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.zibby || pkg.bin?.zb;
|
|
59
|
+
if (!binEntry) throw new Error('@zibby/cli package.json: missing "bin" field');
|
|
60
|
+
return join(pkgPath.replace(/[/\\]package\.json$/, ''), binEntry);
|
|
61
|
+
})();
|
|
45
62
|
|
|
46
63
|
// Mirrors @zibby/cli's config storage. We read/write the same file so the
|
|
47
64
|
// CLI commands we shell out to pick up our login state, and vice versa.
|
|
@@ -83,7 +100,8 @@ function clearSession() {
|
|
|
83
100
|
/**
|
|
84
101
|
* Look up a project's per-project API token (project-scoped, what workflow
|
|
85
102
|
* commands need). Returns null when the project isn't in the saved list
|
|
86
|
-
* (caller should prompt the user to
|
|
103
|
+
* (caller should prompt the user to re-run zibby_login to refresh the
|
|
104
|
+
* cached project list).
|
|
87
105
|
*/
|
|
88
106
|
function getProjectApiToken(projectId) {
|
|
89
107
|
return getProjects().find((p) => p.projectId === projectId)?.apiToken || null;
|
|
@@ -172,7 +190,7 @@ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
|
172
190
|
|
|
173
191
|
const server = new McpServer({
|
|
174
192
|
name: 'zibby-cli',
|
|
175
|
-
version: '0.
|
|
193
|
+
version: '0.3.0',
|
|
176
194
|
});
|
|
177
195
|
|
|
178
196
|
// ── Tool: login ─────────────────────────────────────────────────────────
|
|
@@ -181,7 +199,7 @@ const server = new McpServer({
|
|
|
181
199
|
// Writes session + projects to ~/.zibby/config.json on success.
|
|
182
200
|
server.tool(
|
|
183
201
|
'zibby_login',
|
|
184
|
-
'Log in to Zibby. Opens the user\'s browser to the Zibby login page. The user authorizes in the browser; this tool polls until the auth completes and saves the session to ~/.zibby/config.json. Subsequent tool calls
|
|
202
|
+
'Log in to Zibby. Opens the user\'s browser to the Zibby login page. The user authorizes in the browser; this tool polls until the auth completes and saves the session + project API tokens to ~/.zibby/config.json. Subsequent local tool calls (deploy, download) use the saved credentials automatically. Re-run to refresh the cached project list after granting access to a new project.',
|
|
185
203
|
{},
|
|
186
204
|
async () => {
|
|
187
205
|
// If already logged in, short-circuit — agent shouldn't trigger a
|
|
@@ -230,7 +248,7 @@ server.tool(
|
|
|
230
248
|
if (result.status === 'authorized') {
|
|
231
249
|
// Fetch projects to seed the saved list (workflow ops need per-
|
|
232
250
|
// project apiTokens). Best-effort — if it fails we still save the
|
|
233
|
-
// session token
|
|
251
|
+
// session token; the user can re-run zibby_login to refresh.
|
|
234
252
|
let projects = [];
|
|
235
253
|
try {
|
|
236
254
|
const pRes = await fetch(`${API_BASE}/projects`, {
|
|
@@ -276,9 +294,13 @@ server.tool(
|
|
|
276
294
|
);
|
|
277
295
|
|
|
278
296
|
// ── Tool: status ────────────────────────────────────────────────────────
|
|
297
|
+
// Local-only inspector for which session is active on this machine.
|
|
298
|
+
// (There is also a remote zibby_status served by the Zibby Remote MCP,
|
|
299
|
+
// but only this one can see the local file state — i.e. which login the
|
|
300
|
+
// shelled-out CLI commands will use.)
|
|
279
301
|
server.tool(
|
|
280
302
|
'zibby_status',
|
|
281
|
-
'Show
|
|
303
|
+
'Show the local Zibby session info from ~/.zibby/config.json: who is logged in, how many projects are cached locally for shell-out commands (deploy/download), and whether the session token is still valid against the Zibby API. Use this to confirm WHICH account/session the local tools (deploy/download/scaffold) will use.',
|
|
282
304
|
{},
|
|
283
305
|
async () => {
|
|
284
306
|
const token = getSessionToken();
|
|
@@ -302,44 +324,21 @@ server.tool(
|
|
|
302
324
|
}
|
|
303
325
|
);
|
|
304
326
|
|
|
305
|
-
// ── Tool: list projects ─────────────────────────────────────────────────
|
|
306
|
-
server.tool(
|
|
307
|
-
'zibby_list_projects',
|
|
308
|
-
'List the Zibby projects the logged-in user has access to. Returns {projectId, name} pairs. Use the projectId in subsequent workflow tool calls.',
|
|
309
|
-
{},
|
|
310
|
-
async () => {
|
|
311
|
-
const token = getSessionToken();
|
|
312
|
-
if (!token) return { isError: true, content: [{ type: 'text', text: 'Not logged in. Call zibby_login first.' }] };
|
|
313
|
-
// Pull fresh from API and refresh the local cache while we're at it.
|
|
314
|
-
const res = await fetch(`${API_BASE}/projects`, {
|
|
315
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
316
|
-
});
|
|
317
|
-
if (!res.ok) {
|
|
318
|
-
return { isError: true, content: [{ type: 'text', text: `Failed to list projects: ${res.status}` }] };
|
|
319
|
-
}
|
|
320
|
-
const data = await res.json();
|
|
321
|
-
const projects = (data.projects || []).map((p) => ({
|
|
322
|
-
projectId: p.projectId,
|
|
323
|
-
name: p.name,
|
|
324
|
-
apiToken: p.apiToken, // saved locally for shell-out, not surfaced below
|
|
325
|
-
}));
|
|
326
|
-
saveConfig({ ...loadConfig(), projects });
|
|
327
|
-
return jsonResult(projects.map(({ projectId, name }) => ({ projectId, name })));
|
|
328
|
-
}
|
|
329
|
-
);
|
|
330
|
-
|
|
331
327
|
// ── Tool: list templates ────────────────────────────────────────────────
|
|
328
|
+
// Local because @zibby/cli reads the bundled template manifest from its
|
|
329
|
+
// own node_modules (no network call).
|
|
332
330
|
server.tool(
|
|
333
331
|
'zibby_list_templates',
|
|
334
|
-
'List the official Zibby workflow templates available to scaffold (browser-test-automation, code-analysis, generate-test-cases, etc.). These are the same templates the marketplace deploys.',
|
|
332
|
+
'List the official Zibby workflow templates available to scaffold (browser-test-automation, code-analysis, generate-test-cases, etc.). These are the same templates the marketplace deploys. Reads the manifest bundled with the local @zibby/cli — no network call.',
|
|
335
333
|
{},
|
|
336
334
|
async () => cliResult(await runCli(['template', 'list']))
|
|
337
335
|
);
|
|
338
336
|
|
|
339
337
|
// ── Tool: scaffold workflow ─────────────────────────────────────────────
|
|
338
|
+
// Writes files to .zibby/workflows/<name>/ in the user's cwd.
|
|
340
339
|
server.tool(
|
|
341
340
|
'zibby_scaffold_workflow',
|
|
342
|
-
'Scaffold a new workflow into the current project\'s .zibby/workflows/<name>/ directory from an official template. Generates graph.mjs, nodes/, state.js, and package.json. Use zibby_list_templates first to see options.',
|
|
341
|
+
'Scaffold a new workflow into the current project\'s .zibby/workflows/<name>/ directory from an official template. Generates graph.mjs, nodes/, state.js, and package.json on the user\'s local disk. Use zibby_list_templates first to see options.',
|
|
343
342
|
{
|
|
344
343
|
name: z.string().min(1).describe('Local workflow folder name (kebab-case)'),
|
|
345
344
|
template: z.enum(['browser-test-automation', 'code-analysis', 'generate-test-cases'])
|
|
@@ -355,43 +354,26 @@ server.tool(
|
|
|
355
354
|
);
|
|
356
355
|
|
|
357
356
|
// ── Tool: validate workflow ─────────────────────────────────────────────
|
|
357
|
+
// Reads local workflow files + spawns the local validator.
|
|
358
358
|
server.tool(
|
|
359
359
|
'zibby_validate_workflow',
|
|
360
|
-
'Static-check a local workflow (.zibby/workflows/<name>/): graph topology, state schema, skill references. Fast (~30ms) — runs entirely locally, no API call. Run this before deploy to catch obvious errors.',
|
|
360
|
+
'Static-check a local workflow (.zibby/workflows/<name>/): graph topology, state schema, skill references. Fast (~30ms) — runs entirely locally against files on disk, no API call. Run this before deploy to catch obvious errors.',
|
|
361
361
|
{
|
|
362
362
|
name: z.string().min(1).describe('Workflow folder name under .zibby/workflows/'),
|
|
363
363
|
},
|
|
364
364
|
async ({ name }) => cliResult(await runCli(['workflow', 'validate', name]))
|
|
365
365
|
);
|
|
366
366
|
|
|
367
|
-
// ── Tool: list workflows ────────────────────────────────────────────────
|
|
368
|
-
server.tool(
|
|
369
|
-
'zibby_list_workflows',
|
|
370
|
-
'List workflows. Defaults to interleaving local (.zibby/workflows/) and remote (deployed to a project). Pass projectId to filter remote to a specific project.',
|
|
371
|
-
{
|
|
372
|
-
projectId: z.string().optional().describe('Optional — limit remote results to this project'),
|
|
373
|
-
scope: z.enum(['all', 'local', 'remote']).optional().default('all')
|
|
374
|
-
.describe('all = local + remote (default), local = only local files, remote = only deployed'),
|
|
375
|
-
},
|
|
376
|
-
async ({ projectId, scope }) => {
|
|
377
|
-
const args = ['workflow', 'list'];
|
|
378
|
-
if (scope === 'local') args.push('--local-only');
|
|
379
|
-
if (scope === 'remote') args.push('--remote-only');
|
|
380
|
-
if (projectId) args.push('--project', projectId);
|
|
381
|
-
const apiKey = projectId ? getProjectApiToken(projectId) : null;
|
|
382
|
-
return cliResult(await runCli(args, {
|
|
383
|
-
extraEnv: apiKey ? { ZIBBY_API_KEY: apiKey } : {},
|
|
384
|
-
}));
|
|
385
|
-
}
|
|
386
|
-
);
|
|
387
|
-
|
|
388
367
|
// ── Tool: deploy workflow ───────────────────────────────────────────────
|
|
368
|
+
// Local-essential because the bundling step (zip the .zibby/workflows/<name>/
|
|
369
|
+
// folder + walk its node_modules) happens on the user's machine before
|
|
370
|
+
// upload. The remote MCP can't see those files.
|
|
389
371
|
server.tool(
|
|
390
372
|
'zibby_deploy_workflow',
|
|
391
|
-
'Deploy a local workflow (.zibby/workflows/<name>/) to Zibby Cloud under the given project. Returns the workflow UUID + version on success. Use zibby_validate_workflow first to catch errors fast.',
|
|
373
|
+
'Deploy a local workflow (.zibby/workflows/<name>/) to Zibby Cloud under the given project. Bundles the local workflow folder + dependencies, then uploads. Returns the workflow UUID + version on success. Use zibby_validate_workflow first to catch errors fast.',
|
|
392
374
|
{
|
|
393
375
|
name: z.string().min(1).describe('Local workflow folder name'),
|
|
394
|
-
projectId: z.string().min(1).describe('Project to deploy under (
|
|
376
|
+
projectId: z.string().min(1).describe('Project to deploy under (use the Zibby Remote MCP\'s zibby_list_projects to discover)'),
|
|
395
377
|
force: z.boolean().optional().default(false)
|
|
396
378
|
.describe('Re-deploy even if source checksum is unchanged'),
|
|
397
379
|
warm: z.number().int().min(1).max(5).optional()
|
|
@@ -400,7 +382,7 @@ server.tool(
|
|
|
400
382
|
async ({ name, projectId, force, warm }) => {
|
|
401
383
|
const apiKey = getProjectApiToken(projectId);
|
|
402
384
|
if (!apiKey) {
|
|
403
|
-
return { isError: true, content: [{ type: 'text', text: `No API token cached for project ${projectId}. Run
|
|
385
|
+
return { isError: true, content: [{ type: 'text', text: `No API token cached for project ${projectId}. Run zibby_login (or zibby_logout + zibby_login) to refresh the local project list.` }] };
|
|
404
386
|
}
|
|
405
387
|
const args = ['workflow', 'deploy', name, '--project', projectId];
|
|
406
388
|
if (force) args.push('--force');
|
|
@@ -409,64 +391,15 @@ server.tool(
|
|
|
409
391
|
}
|
|
410
392
|
);
|
|
411
393
|
|
|
412
|
-
// ── Tool: trigger workflow ──────────────────────────────────────────────
|
|
413
|
-
server.tool(
|
|
414
|
-
'zibby_trigger_workflow',
|
|
415
|
-
'Trigger an already-deployed workflow by UUID. Pass input params as a JSON object. Returns the jobId — pass to zibby_workflow_logs to read execution output.',
|
|
416
|
-
{
|
|
417
|
-
uuid: z.string().min(1).describe('Workflow UUID (from zibby_list_workflows or zibby_deploy_workflow output)'),
|
|
418
|
-
projectId: z.string().min(1).describe('Project the workflow lives under'),
|
|
419
|
-
input: z.record(z.string(), z.any()).optional().default({})
|
|
420
|
-
.describe('Input params for the workflow — shape depends on the workflow\'s state schema'),
|
|
421
|
-
idempotencyKey: z.string().optional()
|
|
422
|
-
.describe('Optional idempotency key — same key + same input = same job, no duplicate execution'),
|
|
423
|
-
},
|
|
424
|
-
async ({ uuid, projectId, input, idempotencyKey }) => {
|
|
425
|
-
const apiKey = getProjectApiToken(projectId);
|
|
426
|
-
if (!apiKey) {
|
|
427
|
-
return { isError: true, content: [{ type: 'text', text: `No API token cached for project ${projectId}. Run zibby_list_projects.` }] };
|
|
428
|
-
}
|
|
429
|
-
const args = ['workflow', 'trigger', uuid, '--project', projectId, '--input', JSON.stringify(input || {})];
|
|
430
|
-
if (idempotencyKey) args.push('--idempotency-key', idempotencyKey);
|
|
431
|
-
return cliResult(await runCli(args, { extraEnv: { ZIBBY_API_KEY: apiKey } }));
|
|
432
|
-
}
|
|
433
|
-
);
|
|
434
|
-
|
|
435
|
-
// ── Tool: workflow logs ─────────────────────────────────────────────────
|
|
436
|
-
server.tool(
|
|
437
|
-
'zibby_workflow_logs',
|
|
438
|
-
'Fetch the most recent N log lines from a workflow execution. One-shot — does NOT stream. For long-running runs, call repeatedly. Either jobId OR workflowName is required.',
|
|
439
|
-
{
|
|
440
|
-
projectId: z.string().min(1).describe('Project the run lives under'),
|
|
441
|
-
jobId: z.string().optional().describe('Specific job to fetch logs for (returned by zibby_trigger_workflow)'),
|
|
442
|
-
workflowName: z.string().optional().describe('Alternative to jobId — fetches the latest run for this workflow name'),
|
|
443
|
-
lines: z.number().int().min(1).max(5000).optional().default(500)
|
|
444
|
-
.describe('Max log lines to fetch'),
|
|
445
|
-
},
|
|
446
|
-
async ({ projectId, jobId, workflowName, lines }) => {
|
|
447
|
-
if (!jobId && !workflowName) {
|
|
448
|
-
return { isError: true, content: [{ type: 'text', text: 'Either jobId or workflowName is required.' }] };
|
|
449
|
-
}
|
|
450
|
-
const apiKey = getProjectApiToken(projectId);
|
|
451
|
-
if (!apiKey) {
|
|
452
|
-
return { isError: true, content: [{ type: 'text', text: `No API token cached for project ${projectId}. Run zibby_list_projects.` }] };
|
|
453
|
-
}
|
|
454
|
-
const args = ['workflow', 'logs'];
|
|
455
|
-
if (jobId) args.push(jobId);
|
|
456
|
-
args.push('--project', projectId, '--lines', String(lines));
|
|
457
|
-
if (workflowName) args.push('--workflow', workflowName);
|
|
458
|
-
return cliResult(await runCli(args, { extraEnv: { ZIBBY_API_KEY: apiKey } }));
|
|
459
|
-
}
|
|
460
|
-
);
|
|
461
|
-
|
|
462
394
|
// ── Tool: run workflow locally ──────────────────────────────────────────
|
|
395
|
+
// Spawns a local node process to run the workflow against local files.
|
|
463
396
|
server.tool(
|
|
464
397
|
'zibby_run_workflow_local',
|
|
465
398
|
'Run a local workflow (.zibby/workflows/<name>/) one-shot on the user\'s machine. Does NOT touch the cloud — used for debugging graph.mjs / node code before deploying. Output includes per-node state transitions.',
|
|
466
399
|
{
|
|
467
400
|
name: z.string().min(1).describe('Local workflow folder name'),
|
|
468
401
|
input: z.record(z.string(), z.any()).optional().default({})
|
|
469
|
-
.describe('Input params (
|
|
402
|
+
.describe('Input params (JSON object passed to the workflow\'s entry node)'),
|
|
470
403
|
},
|
|
471
404
|
async ({ name, input }) => {
|
|
472
405
|
const args = ['workflow', 'run', name, '--input', JSON.stringify(input || {})];
|
|
@@ -492,7 +425,7 @@ server.tool(
|
|
|
492
425
|
async ({ uuid, projectId, dest, force }) => {
|
|
493
426
|
const apiKey = getProjectApiToken(projectId);
|
|
494
427
|
if (!apiKey) {
|
|
495
|
-
return { isError: true, content: [{ type: 'text', text: `No API token cached for project ${projectId}. Run
|
|
428
|
+
return { isError: true, content: [{ type: 'text', text: `No API token cached for project ${projectId}. Run zibby_login (or zibby_logout + zibby_login) to refresh the local project list.` }] };
|
|
496
429
|
}
|
|
497
430
|
const args = ['workflow', 'download', uuid, '--dest', dest];
|
|
498
431
|
if (force) args.push('--force');
|
|
@@ -500,243 +433,234 @@ server.tool(
|
|
|
500
433
|
}
|
|
501
434
|
);
|
|
502
435
|
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
// These wrap the (currently in development) Zibby control-plane endpoints
|
|
507
|
-
// for deploying / managing hosted app instances. Each instance runs the
|
|
508
|
-
// open-source agent-ops daemon (github.com/ZibbyHQ/agent-ops) as a sidecar
|
|
509
|
-
// — the daemon's own MCP server is what the user's local agent talks to
|
|
510
|
-
// for per-instance operations. The tools here cover the cross-instance
|
|
511
|
-
// surface that lives in Zibby's control plane.
|
|
512
|
-
//
|
|
513
|
-
// Backend endpoints these wrap are still being built; the MCP tools are
|
|
514
|
-
// shipped now so client-side wiring lands first. Each tool will surface a
|
|
515
|
-
// clear "control plane not yet deployed" error until the backend ships.
|
|
516
|
-
|
|
517
|
-
const APPS_API_BASE = `${API_BASE}/apps`;
|
|
518
|
-
|
|
519
|
-
async function callAppsAPI(path, opts = {}) {
|
|
520
|
-
// /catalog and /catalog/{appType} are public — let unauthenticated
|
|
521
|
-
// calls through (server enforces auth on the rest). Auth-required
|
|
522
|
-
// endpoints return 401 from the backend and we surface that to the
|
|
523
|
-
// tool caller; no need to gate here.
|
|
524
|
-
const token = getSessionToken();
|
|
525
|
-
const url = `${APPS_API_BASE}${path}`;
|
|
526
|
-
const init = {
|
|
527
|
-
method: opts.method || 'GET',
|
|
528
|
-
headers: {
|
|
529
|
-
'Content-Type': 'application/json',
|
|
530
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
531
|
-
...(opts.headers || {}),
|
|
532
|
-
},
|
|
533
|
-
};
|
|
534
|
-
if (opts.body) init.body = JSON.stringify(opts.body);
|
|
535
|
-
try {
|
|
536
|
-
const res = await fetch(url, init);
|
|
537
|
-
const text = await res.text().catch(() => '');
|
|
538
|
-
let body = null;
|
|
539
|
-
try { body = text ? JSON.parse(text) : null; } catch { body = text; }
|
|
540
|
-
return { status: res.status, body, ok: res.ok };
|
|
541
|
-
} catch (err) {
|
|
542
|
-
return { error: err.message, status: 0 };
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// ── Tool: deploy app ────────────────────────────────────────────────────
|
|
547
|
-
server.tool(
|
|
548
|
-
'zibby_deploy_app',
|
|
549
|
-
'Deploy a marketplace app (n8n, Grafana, Open WebUI, …) into a Zibby project. ' +
|
|
550
|
-
'Returns 202 + the instanceId + the public URL. Once provisioning finishes the URL serves the app; ' +
|
|
551
|
-
'use the zibby_app_* tools to interact with its ops agent. No second MCP install needed — every ' +
|
|
552
|
-
'app tool proxies through the Zibby backend.',
|
|
553
|
-
{
|
|
554
|
-
appType: z.string().min(1).describe('Catalog id (use zibby_list_app_catalog to see options, e.g. "n8n")'),
|
|
555
|
-
projectId: z.string().min(1).describe('Zibby project to deploy under'),
|
|
556
|
-
name: z.string().optional().describe('Optional display name for the instance'),
|
|
557
|
-
},
|
|
558
|
-
async ({ appType, projectId, name }) => {
|
|
559
|
-
const res = await callAppsAPI('/deploy', {
|
|
560
|
-
method: 'POST',
|
|
561
|
-
body: { appType, projectId, name },
|
|
562
|
-
});
|
|
563
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
564
|
-
if (!res.ok) {
|
|
565
|
-
const detail = typeof res.body === 'string' ? res.body : JSON.stringify(res.body || {});
|
|
566
|
-
return { isError: true, content: [{ type: 'text', text: `Deploy failed (${res.status}): ${detail}` }] };
|
|
567
|
-
}
|
|
568
|
-
return jsonResult(res.body);
|
|
569
|
-
}
|
|
570
|
-
);
|
|
436
|
+
// ─── Apps: Managed App instances (hosted n8n / grafana / gas-town etc) ─
|
|
437
|
+
// Five tools mirroring the workflow surface above. Each shells out to
|
|
438
|
+
// `zibby app …` and surfaces the CLI's text output to the agent.
|
|
571
439
|
|
|
572
|
-
// ── Tool: list
|
|
440
|
+
// ── Tool: list app templates (the catalog). Public endpoint — no auth.
|
|
573
441
|
server.tool(
|
|
574
|
-
'
|
|
575
|
-
'List
|
|
576
|
-
'tagline, category, version, ratingAvg, installCount). Public — no Zibby login required.',
|
|
442
|
+
'zibby_list_app_templates',
|
|
443
|
+
'List the official Zibby app catalog (n8n, grafana, gas-town, open-webui, …). These are the same apps the marketplace deploys. No auth required — anyone can browse the catalog before signing in. Each entry exposes an `architectures` array listing the CPU arches it supports (x86_64 and/or arm64); pass the first entry to zibby_deploy_app\'s `architecture` arg to honor operator preference, or omit it entirely to let the server pick.',
|
|
577
444
|
{},
|
|
578
|
-
async () =>
|
|
579
|
-
const res = await callAppsAPI('/catalog');
|
|
580
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
581
|
-
if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Catalog failed (${res.status})` }] };
|
|
582
|
-
return jsonResult(res.body);
|
|
583
|
-
}
|
|
445
|
+
async () => cliResult(await runCli(['app', 'templates']))
|
|
584
446
|
);
|
|
585
447
|
|
|
586
|
-
// ── Tool: list apps
|
|
448
|
+
// ── Tool: list deployed apps. Project-scoped when projectId is given;
|
|
449
|
+
// otherwise lists every instance under the caller's account.
|
|
587
450
|
server.tool(
|
|
588
451
|
'zibby_list_apps',
|
|
589
|
-
'List
|
|
452
|
+
'List the user\'s deployed app instances (live status, app type/version, project). Pass projectId to scope to one project, otherwise returns everything under the account.',
|
|
590
453
|
{
|
|
591
|
-
projectId: z.string().min(1).
|
|
454
|
+
projectId: z.string().min(1).optional()
|
|
455
|
+
.describe('Optional project ID — filter to one project\'s instances'),
|
|
592
456
|
},
|
|
593
457
|
async ({ projectId }) => {
|
|
594
|
-
const
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
|
|
458
|
+
const args = ['app', 'list'];
|
|
459
|
+
if (projectId) args.push('--project', projectId);
|
|
460
|
+
// Use the project API token when a project is supplied (matches the
|
|
461
|
+
// workflow tools' pattern); otherwise let the CLI fall back to the
|
|
462
|
+
// session token from ~/.zibby/config.json.
|
|
463
|
+
const extraEnv = {};
|
|
464
|
+
if (projectId) {
|
|
465
|
+
const apiKey = getProjectApiToken(projectId);
|
|
466
|
+
if (apiKey) extraEnv.ZIBBY_API_KEY = apiKey;
|
|
467
|
+
}
|
|
468
|
+
return cliResult(await runCli(args, { extraEnv }));
|
|
598
469
|
}
|
|
599
470
|
);
|
|
600
471
|
|
|
601
|
-
// ── Tool: app
|
|
472
|
+
// ── Tool: deploy an app. Picks a catalog entry and provisions a new
|
|
473
|
+
// hosted instance under the target project. The CLI's interactive
|
|
474
|
+
// project picker is skipped here because we require projectId up front.
|
|
602
475
|
server.tool(
|
|
603
|
-
'
|
|
604
|
-
'
|
|
476
|
+
'zibby_deploy_app',
|
|
477
|
+
'Deploy an app from the catalog (appType) OR a custom install described in natural language (goal). Catalog is curated by Zibby and license-safe to host; goal-based deploys are user-directed installs where the user (not Zibby) chooses what to install. License-sensitive apps like n8n (SUL) should ONLY be deployed via goal at user\'s direction, never via catalog. Provisions a new hosted instance (ECS Service + ALB target + agent-ops sidecar) and returns the new instanceId + public URL. Pass EXACTLY ONE of appType or goal. Use zibby_list_app_templates first to see available appType values AND each entry\'s `architectures` array. Optionally pass provider="codex" to run with the OpenAI Codex agent instead of the default Claude agent — requires the user to have an OpenAI API key staged via zibby_set_openai_credential. Optionally pass architecture="arm64" to run on AWS Graviton (~20% greener at the SAME price — operator pockets the compute savings, user pays the same per-minute rate). Defaults to the catalog\'s first listed architecture (operator-preferred order). If the catalog entry doesn\'t support the requested arch the server returns 400 with a hint listing what it does support.',
|
|
605
478
|
{
|
|
606
|
-
|
|
479
|
+
appType: z.string().min(1).optional().describe('Catalog id (e.g. "grafana", "gastown"). MUTUALLY EXCLUSIVE with goal — pass exactly one. Use this when the user wants something from the curated catalog.'),
|
|
480
|
+
goal: z.string().min(1).optional().describe('Free-form description of what to install (e.g. "install n8n on port 5678 with sqlite persistence"). MUTUALLY EXCLUSIVE with appType. Use this when the user wants something NOT in catalog OR has custom config needs — the agent-ops bootstrap will follow the description to install whatever it describes. Examples: "install n8n", "set up an Outline wiki at /wiki", "deploy a basic Rails app from <repo URL>". The user is responsible for any license terms of software they install via this path.'),
|
|
481
|
+
projectId: z.string().min(1).describe('Project the instance attaches to'),
|
|
482
|
+
name: z.string().min(1).optional().describe('Display name for the instance (defaults to appType, or the first line of goal)'),
|
|
483
|
+
provider: z.enum(['claude', 'codex']).optional().describe('Agent provider. Default "claude" (Anthropic). "codex" runs the OpenAI Codex agent and requires an OpenAI API key in workspace-credentials.'),
|
|
484
|
+
architecture: z.enum(['x86_64', 'arm64']).optional().describe('CPU architecture for the Fargate task. "arm64" runs on AWS Graviton — ~20% cheaper compute (same price to user). "x86_64" is the historical default + widest catalog compatibility. Omit to accept the catalog tile\'s preferred arch (first entry in its `architectures` array — usually arm64 for tiles that support it).'),
|
|
607
485
|
},
|
|
608
|
-
async ({
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
486
|
+
async ({ appType, goal, projectId, name, provider, architecture }) => {
|
|
487
|
+
// Enforce mutual exclusivity client-side so the agent gets a clear
|
|
488
|
+
// error before we burn an HTTP round-trip. Backend enforces the
|
|
489
|
+
// same invariant (apps.js::deployApp) but this gives a faster
|
|
490
|
+
// tighter error loop for the agent.
|
|
491
|
+
if (appType && goal) {
|
|
492
|
+
return { isError: true, content: [{ type: 'text', text: 'Pass either appType (catalog id) OR goal (free-form description), not both.' }] };
|
|
493
|
+
}
|
|
494
|
+
if (!appType && !goal) {
|
|
495
|
+
return { isError: true, content: [{ type: 'text', text: 'Pass either appType (catalog id, see zibby_list_app_templates) OR goal (free-form description) to describe what to deploy.' }] };
|
|
496
|
+
}
|
|
497
|
+
const apiKey = getProjectApiToken(projectId);
|
|
498
|
+
if (!apiKey) {
|
|
499
|
+
return { isError: true, content: [{ type: 'text', text: `No API token cached for project ${projectId}. Run zibby_login to refresh the local project list.` }] };
|
|
500
|
+
}
|
|
501
|
+
// CLI accepts EITHER `app deploy <appType>` OR `app deploy --goal <text>`
|
|
502
|
+
// (see packages/cli/bin/zibby.js); we forward whichever the agent picked.
|
|
503
|
+
const args = ['app', 'deploy'];
|
|
504
|
+
if (appType) args.push(appType);
|
|
505
|
+
if (goal) args.push('--goal', goal);
|
|
506
|
+
args.push('--project', projectId);
|
|
507
|
+
if (name) args.push('--name', name);
|
|
508
|
+
if (provider) args.push('--provider', provider);
|
|
509
|
+
if (architecture) args.push('--arch', architecture);
|
|
510
|
+
return cliResult(await runCli(args, { extraEnv: { ZIBBY_API_KEY: apiKey } }));
|
|
613
511
|
}
|
|
614
512
|
);
|
|
615
513
|
|
|
616
|
-
// ── Tool:
|
|
514
|
+
// ── Tool: status of a single instance — live ECS state, public URL,
|
|
515
|
+
// resources, app version, project.
|
|
617
516
|
server.tool(
|
|
618
|
-
'
|
|
619
|
-
'
|
|
517
|
+
'zibby_get_app',
|
|
518
|
+
'Show one deployed app instance: status (running / pending / failed), running task count, app type+version, resources, public URL, project. Use when the user asks "is my app up?" or "what URL is it on?".',
|
|
620
519
|
{
|
|
621
|
-
instanceId: z.string().min(1).describe('Instance
|
|
622
|
-
|
|
623
|
-
|
|
520
|
+
instanceId: z.string().min(1).describe('Instance ID returned from zibby_deploy_app or zibby_list_apps'),
|
|
521
|
+
projectId: z.string().min(1).optional()
|
|
522
|
+
.describe('Project the instance belongs to (lets the tool pick the right cached API token; falls back to session token when omitted)'),
|
|
624
523
|
},
|
|
625
|
-
async ({ instanceId,
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
return textResult(typeof res.body === 'string' ? res.body : JSON.stringify(res.body, null, 2));
|
|
524
|
+
async ({ instanceId, projectId }) => {
|
|
525
|
+
const extraEnv = {};
|
|
526
|
+
if (projectId) {
|
|
527
|
+
const apiKey = getProjectApiToken(projectId);
|
|
528
|
+
if (apiKey) extraEnv.ZIBBY_API_KEY = apiKey;
|
|
529
|
+
}
|
|
530
|
+
return cliResult(await runCli(['app', 'status', instanceId], { extraEnv }));
|
|
633
531
|
}
|
|
634
532
|
);
|
|
635
533
|
|
|
636
|
-
// ──
|
|
637
|
-
//
|
|
638
|
-
// Every tool below routes through POST /apps/{id}/mcp on the Zibby
|
|
639
|
-
// backend, which forwards the JSON-RPC body to the per-instance
|
|
640
|
-
// agent-ops daemon using the bridgeToken stored in DDB. This means
|
|
641
|
-
// users never copy a per-instance token into a local mcp.json — one
|
|
642
|
-
// MCP install (@zibby/mcp-cli) covers every instance they own.
|
|
643
|
-
//
|
|
644
|
-
// Each tool wraps `tools/call` with the daemon's matching agent_*
|
|
645
|
-
// builtin (agent_run_now, agent_set_mission, etc.). Helper below
|
|
646
|
-
// keeps the JSON-RPC shape and error handling in one place.
|
|
647
|
-
async function callDaemonTool(instanceId, daemonToolName, args = {}) {
|
|
648
|
-
const body = {
|
|
649
|
-
jsonrpc: '2.0',
|
|
650
|
-
id: Date.now(),
|
|
651
|
-
method: 'tools/call',
|
|
652
|
-
params: { name: daemonToolName, arguments: args },
|
|
653
|
-
};
|
|
654
|
-
const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}/mcp`, {
|
|
655
|
-
method: 'POST',
|
|
656
|
-
body,
|
|
657
|
-
});
|
|
658
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
659
|
-
if (!res.ok) {
|
|
660
|
-
const detail = typeof res.body === 'string' ? res.body : JSON.stringify(res.body || {});
|
|
661
|
-
return { isError: true, content: [{ type: 'text', text: `Daemon ${daemonToolName} failed (${res.status}): ${detail}` }] };
|
|
662
|
-
}
|
|
663
|
-
// The daemon returns a JSON-RPC envelope; surface `result` if present,
|
|
664
|
-
// otherwise the raw body.
|
|
665
|
-
const payload = res.body?.result ?? res.body;
|
|
666
|
-
return jsonResult(payload);
|
|
667
|
-
}
|
|
668
|
-
|
|
534
|
+
// ── Tool: tail recent logs for a single instance. One-shot snapshot
|
|
535
|
+
// (no streaming over MCP — agents can call again for fresh batches).
|
|
669
536
|
server.tool(
|
|
670
|
-
'
|
|
671
|
-
'
|
|
672
|
-
'Useful for "did the nightly health-check pass?" type questions.',
|
|
537
|
+
'zibby_get_app_logs',
|
|
538
|
+
'Fetch the most recent log lines from a deployed app instance. One-shot snapshot (the user should call again for new lines). Useful for diagnosing why an app is stuck, failed to provision, or to confirm a feature works end-to-end. Returns a string with one event per line, prefixed by ISO timestamp.',
|
|
673
539
|
{
|
|
674
|
-
instanceId: z.string().min(1).describe('Instance
|
|
675
|
-
|
|
540
|
+
instanceId: z.string().min(1).describe('Instance ID returned from zibby_deploy_app or zibby_list_apps'),
|
|
541
|
+
projectId: z.string().min(1).optional().describe('Project the instance belongs to (picks the right cached API token)'),
|
|
542
|
+
lines: z.number().int().min(1).max(5000).optional().default(200)
|
|
543
|
+
.describe('Max number of recent lines to fetch (default 200, max 5000)'),
|
|
676
544
|
},
|
|
677
|
-
async ({ instanceId,
|
|
545
|
+
async ({ instanceId, projectId, lines }) => {
|
|
546
|
+
const extraEnv = {};
|
|
547
|
+
if (projectId) {
|
|
548
|
+
const apiKey = getProjectApiToken(projectId);
|
|
549
|
+
if (apiKey) extraEnv.ZIBBY_API_KEY = apiKey;
|
|
550
|
+
}
|
|
551
|
+
const args = ['app', 'logs', instanceId];
|
|
552
|
+
if (lines) args.push('--lines', String(lines));
|
|
553
|
+
return cliResult(await runCli(args, { extraEnv }));
|
|
554
|
+
}
|
|
678
555
|
);
|
|
679
556
|
|
|
557
|
+
// ── Tool: upgrade an instance's agent-ops version in-place.
|
|
558
|
+
// Non-destructive: EFS data, bridgeToken, ALB wiring all preserved.
|
|
559
|
+
// Re-registers the task def with a new image tag, then force-redeploys
|
|
560
|
+
// the ECS Service so the new container picks up the data on the same
|
|
561
|
+
// EFS access point.
|
|
680
562
|
server.tool(
|
|
681
|
-
'
|
|
682
|
-
'
|
|
683
|
-
'the user added). Returns name, cron expression, prompt template.',
|
|
563
|
+
'zibby_upgrade_app',
|
|
564
|
+
'Upgrade a deployed app instance to a new agent-ops image version IN PLACE. EFS data, ALB wiring, and the per-instance bridgeToken are all preserved — this is NOT a destroy + redeploy. ECS rolls the service: new task pulls the new image, swaps over once healthy. Use after a new agent-ops version is published. Returns the new taskDefinitionArn and the resolved image tag.',
|
|
684
565
|
{
|
|
685
|
-
instanceId: z.string().min(1).describe('Instance
|
|
566
|
+
instanceId: z.string().min(1).describe('Instance ID to upgrade'),
|
|
567
|
+
projectId: z.string().min(1).optional().describe('Project the instance belongs to (picks the right cached API token)'),
|
|
568
|
+
version: z.string().min(1).max(40).optional()
|
|
569
|
+
.describe('Pin to a specific agent-ops version tag (e.g. "0.1.16"). Without this, the upgrade picks up whatever\'s in the AppsFleet base task def in SSM.'),
|
|
686
570
|
},
|
|
687
|
-
async ({ instanceId }) =>
|
|
571
|
+
async ({ instanceId, projectId, version }) => {
|
|
572
|
+
const extraEnv = {};
|
|
573
|
+
if (projectId) {
|
|
574
|
+
const apiKey = getProjectApiToken(projectId);
|
|
575
|
+
if (apiKey) extraEnv.ZIBBY_API_KEY = apiKey;
|
|
576
|
+
}
|
|
577
|
+
const args = ['app', 'upgrade', instanceId, '--yes'];
|
|
578
|
+
if (version) args.push('--version', version);
|
|
579
|
+
return cliResult(await runCli(args, { extraEnv }));
|
|
580
|
+
}
|
|
688
581
|
);
|
|
689
582
|
|
|
583
|
+
// ── Tool: destroy an instance. DESTRUCTIVE — the agent must confirm
|
|
584
|
+
// with the user before calling. Passes --yes through to skip the CLI's
|
|
585
|
+
// own interactive confirm (which doesn't work over MCP's stdio).
|
|
690
586
|
server.tool(
|
|
691
|
-
'
|
|
692
|
-
'
|
|
693
|
-
'zibby_app_list_tasks. The daemon kicks off the LLM run async; poll zibby_app_history to see the result.',
|
|
587
|
+
'zibby_destroy_app',
|
|
588
|
+
'Stop and destroy a deployed app instance. DESTRUCTIVE: the running ECS task is stopped, the EFS volume is released, and the instance row is removed. The agent MUST first ask the user for explicit confirmation, then call this with confirm=true.',
|
|
694
589
|
{
|
|
695
|
-
instanceId: z.string().min(1).describe('Instance
|
|
696
|
-
|
|
590
|
+
instanceId: z.string().min(1).describe('Instance ID to destroy'),
|
|
591
|
+
projectId: z.string().min(1).optional().describe('Project the instance belongs to (picks the right cached API token)'),
|
|
592
|
+
confirm: z.literal(true).describe('Must be true. Set only after the user has explicitly approved destroying this instance.'),
|
|
697
593
|
},
|
|
698
|
-
async ({ instanceId,
|
|
594
|
+
async ({ instanceId, projectId }) => {
|
|
595
|
+
const extraEnv = {};
|
|
596
|
+
if (projectId) {
|
|
597
|
+
const apiKey = getProjectApiToken(projectId);
|
|
598
|
+
if (apiKey) extraEnv.ZIBBY_API_KEY = apiKey;
|
|
599
|
+
}
|
|
600
|
+
return cliResult(await runCli(['app', 'destroy', instanceId, '--yes'], { extraEnv }));
|
|
601
|
+
}
|
|
699
602
|
);
|
|
700
603
|
|
|
604
|
+
// ── Tool: paste / replace a Claude credential (oauth or api).
|
|
605
|
+
// The deploy flow is BYOK ONLY — agents that try to deploy an app for a
|
|
606
|
+
// user with no Claude credential get the 428 CLAUDE_CREDENTIAL_REQUIRED
|
|
607
|
+
// error from zibby_deploy_app, then call this tool to onboard.
|
|
608
|
+
//
|
|
609
|
+
// The token comes from the user; the agent should NOT mint or fabricate
|
|
610
|
+
// it (no oauth flow over MCP). Common path: agent asks user to paste a
|
|
611
|
+
// token, user does, agent calls this with the pasted string.
|
|
701
612
|
server.tool(
|
|
702
|
-
'
|
|
703
|
-
'
|
|
704
|
-
'carries across every scheduled task run (e.g. "keep n8n on the latest LTS, never auto-major-upgrade").',
|
|
613
|
+
'zibby_set_claude_credential',
|
|
614
|
+
'Store a Claude credential (oauth long-lived token from `claude setup-token`, or an Anthropic `sk-ant-…` API key) in the user\'s workspace credentials store. KMS-encrypted server-side; the plaintext is sent over HTTPS but never persisted client-side. Auto-detects oauth vs api by token prefix (`sk-ant-oat` / `oat_` → oauth; `sk-ant-api` → api). Use this AFTER the user pastes a token, NOT to mint one yourself. After this succeeds, retry the original zibby_deploy_app call.',
|
|
705
615
|
{
|
|
706
|
-
|
|
616
|
+
token: z.string().min(8).describe('The Claude token the user pasted. Either an OAuth subscription token from `claude setup-token` (long-lived, bills against Claude Code sub) or an Anthropic API key starting with sk-ant-… (bills against Anthropic API credits).'),
|
|
617
|
+
type: z.enum(['oauth', 'api']).optional().describe('Force the credential kind. Omit to auto-detect from token prefix.'),
|
|
707
618
|
},
|
|
708
|
-
async ({
|
|
619
|
+
async ({ token, type }) => {
|
|
620
|
+
const args = ['creds', 'set', 'claude', token];
|
|
621
|
+
if (type) args.push('--type', type);
|
|
622
|
+
return cliResult(await runCli(args));
|
|
623
|
+
}
|
|
709
624
|
);
|
|
710
625
|
|
|
626
|
+
// ── Tool: paste an OpenAI API key for Codex deploys. Mirror of the
|
|
627
|
+
// Claude tool above. Codex v1 is API-key only (no OAuth flow), so the
|
|
628
|
+
// kind is hardcoded api server-side; agents only need to pass the
|
|
629
|
+
// token. The deploy flow is BYOK — agents that try a Codex deploy
|
|
630
|
+
// without an OpenAI cred get a 428 OPENAI_CREDENTIAL_REQUIRED error
|
|
631
|
+
// from zibby_deploy_app, then call this tool to onboard.
|
|
711
632
|
server.tool(
|
|
712
|
-
'
|
|
713
|
-
'
|
|
714
|
-
'changing the mission alters the daemon\'s autonomous behavior on every subsequent task.',
|
|
633
|
+
'zibby_set_openai_credential',
|
|
634
|
+
'Store an OpenAI API key in the user\'s workspace credentials store, for use by the Codex agent on Managed App deploys. KMS-encrypted server-side; the plaintext is sent over HTTPS but never persisted client-side. API-key only (Codex v1 has no OAuth flow). Use this AFTER the user pastes their key, NOT to mint one yourself. After this succeeds, retry the original zibby_deploy_app call with provider="codex".',
|
|
715
635
|
{
|
|
716
|
-
|
|
717
|
-
mission: z.string().min(1).max(4000).describe('Free-text instruction the daemon carries forward'),
|
|
636
|
+
token: z.string().min(8).describe('The OpenAI API key the user pasted (typically starts with sk-…). Mint one at https://platform.openai.com/api-keys. Bills against the user\'s OpenAI account.'),
|
|
718
637
|
},
|
|
719
|
-
async ({
|
|
638
|
+
async ({ token }) => {
|
|
639
|
+
const args = ['creds', 'set', 'openai', token];
|
|
640
|
+
return cliResult(await runCli(args));
|
|
641
|
+
}
|
|
720
642
|
);
|
|
721
643
|
|
|
722
|
-
// ── Tool:
|
|
644
|
+
// ── Tool: rotate the per-instance Claude credential to whatever's
|
|
645
|
+
// currently in the user's workspace-credentials. EFS data + bridgeToken
|
|
646
|
+
// + ALB wiring all preserved; ECS Service rolls the task ~30s.
|
|
647
|
+
//
|
|
648
|
+
// Use after `zibby_set_claude_credential` rotated the workspace cred,
|
|
649
|
+
// to push the change to already-running app instances.
|
|
723
650
|
server.tool(
|
|
724
|
-
'
|
|
725
|
-
'
|
|
726
|
-
'DESTRUCTIVE — the agent MUST confirm with the user and pass confirm:true.',
|
|
651
|
+
'zibby_update_app_credential',
|
|
652
|
+
'Rotate the per-instance Claude credential on a running app instance to whatever is currently stored in the user\'s workspace-credentials. Use after the user has uploaded a new token (via zibby_set_claude_credential) and wants existing instances to pick it up. EFS data, ALB wiring, and the bridgeToken are preserved. The task restarts (~30s) and ECS swaps over.',
|
|
727
653
|
{
|
|
728
|
-
instanceId: z.string().min(1).describe('Instance
|
|
729
|
-
|
|
730
|
-
keepData: z.boolean().optional().default(false).describe('If true, snapshots the EFS before detach so a future redeploy can restore.'),
|
|
654
|
+
instanceId: z.string().min(1).describe('Instance ID to rotate the credential on'),
|
|
655
|
+
projectId: z.string().min(1).optional().describe('Project the instance belongs to (picks the right cached API token)'),
|
|
731
656
|
},
|
|
732
|
-
async ({ instanceId,
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return jsonResult(res.body);
|
|
657
|
+
async ({ instanceId, projectId }) => {
|
|
658
|
+
const extraEnv = {};
|
|
659
|
+
if (projectId) {
|
|
660
|
+
const apiKey = getProjectApiToken(projectId);
|
|
661
|
+
if (apiKey) extraEnv.ZIBBY_API_KEY = apiKey;
|
|
662
|
+
}
|
|
663
|
+
return cliResult(await runCli(['app', 'update-credential', instanceId], { extraEnv }));
|
|
740
664
|
}
|
|
741
665
|
);
|
|
742
666
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/mcp-cli",
|
|
3
|
-
"version": "0.2
|
|
4
|
-
"description": "Zibby
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Zibby local-essential MCP Server — local workflow scaffold/validate/run + deploy/download (bundles local files). Pure-API tools live in the Zibby Remote MCP (api-prod.zibby.app/mcp).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|