@zibby/mcp-cli 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -26
- package/index.js +57 -361
- 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,6 @@ server.tool(
|
|
|
500
433
|
}
|
|
501
434
|
);
|
|
502
435
|
|
|
503
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
504
|
-
// agent-ops integration tools
|
|
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
|
-
const token = getSessionToken();
|
|
521
|
-
if (!token) return { error: 'not logged in', status: 401 };
|
|
522
|
-
const url = `${APPS_API_BASE}${path}`;
|
|
523
|
-
const init = {
|
|
524
|
-
method: opts.method || 'GET',
|
|
525
|
-
headers: {
|
|
526
|
-
'Content-Type': 'application/json',
|
|
527
|
-
Authorization: `Bearer ${token}`,
|
|
528
|
-
...(opts.headers || {}),
|
|
529
|
-
},
|
|
530
|
-
};
|
|
531
|
-
if (opts.body) init.body = JSON.stringify(opts.body);
|
|
532
|
-
try {
|
|
533
|
-
const res = await fetch(url, init);
|
|
534
|
-
const text = await res.text().catch(() => '');
|
|
535
|
-
let body = null;
|
|
536
|
-
try { body = text ? JSON.parse(text) : null; } catch { body = text; }
|
|
537
|
-
return { status: res.status, body, ok: res.ok };
|
|
538
|
-
} catch (err) {
|
|
539
|
-
return { error: err.message, status: 0 };
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// ── Tool: deploy app ────────────────────────────────────────────────────
|
|
544
|
-
server.tool(
|
|
545
|
-
'zibby_deploy_app',
|
|
546
|
-
'Deploy a marketplace app (n8n, Grafana, Open WebUI, …) into a Zibby project. ' +
|
|
547
|
-
'Returns 202 + the instanceId + the public URL. Once provisioning finishes the URL serves the app; ' +
|
|
548
|
-
'use the zibby_app_* tools to interact with its ops agent. No second MCP install needed — every ' +
|
|
549
|
-
'app tool proxies through the Zibby backend.',
|
|
550
|
-
{
|
|
551
|
-
appType: z.string().min(1).describe('Catalog id (use zibby_list_app_catalog to see options, e.g. "n8n")'),
|
|
552
|
-
projectId: z.string().min(1).describe('Zibby project to deploy under'),
|
|
553
|
-
name: z.string().optional().describe('Optional display name for the instance'),
|
|
554
|
-
},
|
|
555
|
-
async ({ appType, projectId, name }) => {
|
|
556
|
-
const res = await callAppsAPI('/deploy', {
|
|
557
|
-
method: 'POST',
|
|
558
|
-
body: { appType, projectId, name },
|
|
559
|
-
});
|
|
560
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
561
|
-
if (!res.ok) {
|
|
562
|
-
const detail = typeof res.body === 'string' ? res.body : JSON.stringify(res.body || {});
|
|
563
|
-
return { isError: true, content: [{ type: 'text', text: `Deploy failed (${res.status}): ${detail}` }] };
|
|
564
|
-
}
|
|
565
|
-
return jsonResult(res.body);
|
|
566
|
-
}
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
// ── Tool: list marketplace catalog ──────────────────────────────────────
|
|
570
|
-
server.tool(
|
|
571
|
-
'zibby_list_app_catalog',
|
|
572
|
-
'List every app available in the Zibby marketplace. Returns tile metadata (appType, displayName, ' +
|
|
573
|
-
'tagline, category, version, ratingAvg, installCount). Public — no Zibby login required.',
|
|
574
|
-
{},
|
|
575
|
-
async () => {
|
|
576
|
-
const res = await callAppsAPI('/catalog');
|
|
577
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
578
|
-
if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Catalog failed (${res.status})` }] };
|
|
579
|
-
return jsonResult(res.body);
|
|
580
|
-
}
|
|
581
|
-
);
|
|
582
|
-
|
|
583
|
-
// ── Tool: list apps ──────────────────────────────────────────────────────
|
|
584
|
-
server.tool(
|
|
585
|
-
'zibby_list_apps',
|
|
586
|
-
'List hosted app instances in a project. Returns each instance\'s id, template, status, and MCP endpoint URL.',
|
|
587
|
-
{
|
|
588
|
-
projectId: z.string().min(1).describe('Zibby project to scope to'),
|
|
589
|
-
},
|
|
590
|
-
async ({ projectId }) => {
|
|
591
|
-
const res = await callAppsAPI(`?projectId=${encodeURIComponent(projectId)}`);
|
|
592
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
593
|
-
if (!res.ok) return { isError: true, content: [{ type: 'text', text: `List failed (${res.status})` }] };
|
|
594
|
-
return jsonResult(res.body);
|
|
595
|
-
}
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
// ── Tool: app status ─────────────────────────────────────────────────────
|
|
599
|
-
server.tool(
|
|
600
|
-
'zibby_app_status',
|
|
601
|
-
'Show detailed status of one hosted app instance: task state, sidecar version, agent-ops heartbeat, last incident.',
|
|
602
|
-
{
|
|
603
|
-
instanceId: z.string().min(1).describe('Instance id (from zibby_list_apps or zibby_deploy_app)'),
|
|
604
|
-
},
|
|
605
|
-
async ({ instanceId }) => {
|
|
606
|
-
const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}`);
|
|
607
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
608
|
-
if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Status failed (${res.status})` }] };
|
|
609
|
-
return jsonResult(res.body);
|
|
610
|
-
}
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
// ── Tool: app logs ───────────────────────────────────────────────────────
|
|
614
|
-
server.tool(
|
|
615
|
-
'zibby_app_logs',
|
|
616
|
-
'Tail the latest N log lines from one hosted app instance. Both the main app container and the agent-ops sidecar log to the same stream; pass `container` to scope.',
|
|
617
|
-
{
|
|
618
|
-
instanceId: z.string().min(1).describe('Instance id'),
|
|
619
|
-
container: z.enum(['app', 'agent-ops']).optional().describe('Which container; defaults to interleaved'),
|
|
620
|
-
lines: z.number().int().min(1).max(5000).optional().default(200).describe('How many recent lines'),
|
|
621
|
-
},
|
|
622
|
-
async ({ instanceId, container, lines }) => {
|
|
623
|
-
const qs = new URLSearchParams();
|
|
624
|
-
if (container) qs.set('container', container);
|
|
625
|
-
if (lines) qs.set('lines', String(lines));
|
|
626
|
-
const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}/logs?${qs.toString()}`);
|
|
627
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
628
|
-
if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Logs failed (${res.status})` }] };
|
|
629
|
-
return textResult(typeof res.body === 'string' ? res.body : JSON.stringify(res.body, null, 2));
|
|
630
|
-
}
|
|
631
|
-
);
|
|
632
|
-
|
|
633
|
-
// ── Daemon MCP proxy ─────────────────────────────────────────────────────
|
|
634
|
-
//
|
|
635
|
-
// Every tool below routes through POST /apps/{id}/mcp on the Zibby
|
|
636
|
-
// backend, which forwards the JSON-RPC body to the per-instance
|
|
637
|
-
// agent-ops daemon using the bridgeToken stored in DDB. This means
|
|
638
|
-
// users never copy a per-instance token into a local mcp.json — one
|
|
639
|
-
// MCP install (@zibby/mcp-cli) covers every instance they own.
|
|
640
|
-
//
|
|
641
|
-
// Each tool wraps `tools/call` with the daemon's matching agent_*
|
|
642
|
-
// builtin (agent_run_now, agent_set_mission, etc.). Helper below
|
|
643
|
-
// keeps the JSON-RPC shape and error handling in one place.
|
|
644
|
-
async function callDaemonTool(instanceId, daemonToolName, args = {}) {
|
|
645
|
-
const body = {
|
|
646
|
-
jsonrpc: '2.0',
|
|
647
|
-
id: Date.now(),
|
|
648
|
-
method: 'tools/call',
|
|
649
|
-
params: { name: daemonToolName, arguments: args },
|
|
650
|
-
};
|
|
651
|
-
const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}/mcp`, {
|
|
652
|
-
method: 'POST',
|
|
653
|
-
body,
|
|
654
|
-
});
|
|
655
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
656
|
-
if (!res.ok) {
|
|
657
|
-
const detail = typeof res.body === 'string' ? res.body : JSON.stringify(res.body || {});
|
|
658
|
-
return { isError: true, content: [{ type: 'text', text: `Daemon ${daemonToolName} failed (${res.status}): ${detail}` }] };
|
|
659
|
-
}
|
|
660
|
-
// The daemon returns a JSON-RPC envelope; surface `result` if present,
|
|
661
|
-
// otherwise the raw body.
|
|
662
|
-
const payload = res.body?.result ?? res.body;
|
|
663
|
-
return jsonResult(payload);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
server.tool(
|
|
667
|
-
'zibby_app_history',
|
|
668
|
-
'Return the recent run history for an instance: each prior task invocation, exit status, and elapsed time. ' +
|
|
669
|
-
'Useful for "did the nightly health-check pass?" type questions.',
|
|
670
|
-
{
|
|
671
|
-
instanceId: z.string().min(1).describe('Instance id'),
|
|
672
|
-
limit: z.number().int().min(1).max(100).optional().default(20),
|
|
673
|
-
},
|
|
674
|
-
async ({ instanceId, limit }) => callDaemonTool(instanceId, 'agent_history', { limit })
|
|
675
|
-
);
|
|
676
|
-
|
|
677
|
-
server.tool(
|
|
678
|
-
'zibby_app_list_tasks',
|
|
679
|
-
'List scheduled tasks configured on an instance (hourly health checks, weekly upgrades, custom cron jobs ' +
|
|
680
|
-
'the user added). Returns name, cron expression, prompt template.',
|
|
681
|
-
{
|
|
682
|
-
instanceId: z.string().min(1).describe('Instance id'),
|
|
683
|
-
},
|
|
684
|
-
async ({ instanceId }) => callDaemonTool(instanceId, 'agent_list_tasks', {})
|
|
685
|
-
);
|
|
686
|
-
|
|
687
|
-
server.tool(
|
|
688
|
-
'zibby_app_run_task_now',
|
|
689
|
-
'Trigger an existing scheduled task to run immediately, out-of-cron. The task name comes from ' +
|
|
690
|
-
'zibby_app_list_tasks. The daemon kicks off the LLM run async; poll zibby_app_history to see the result.',
|
|
691
|
-
{
|
|
692
|
-
instanceId: z.string().min(1).describe('Instance id'),
|
|
693
|
-
taskName: z.string().min(1).describe('Task name from zibby_app_list_tasks'),
|
|
694
|
-
},
|
|
695
|
-
async ({ instanceId, taskName }) => callDaemonTool(instanceId, 'agent_run_now', { name: taskName })
|
|
696
|
-
);
|
|
697
|
-
|
|
698
|
-
server.tool(
|
|
699
|
-
'zibby_app_get_mission',
|
|
700
|
-
'Read the instance\'s persistent "mission" — a free-text instruction the agent-ops daemon ' +
|
|
701
|
-
'carries across every scheduled task run (e.g. "keep n8n on the latest LTS, never auto-major-upgrade").',
|
|
702
|
-
{
|
|
703
|
-
instanceId: z.string().min(1).describe('Instance id'),
|
|
704
|
-
},
|
|
705
|
-
async ({ instanceId }) => callDaemonTool(instanceId, 'agent_get_mission', {})
|
|
706
|
-
);
|
|
707
|
-
|
|
708
|
-
server.tool(
|
|
709
|
-
'zibby_app_set_mission',
|
|
710
|
-
'Set the instance\'s persistent mission. Replaces the prior value. Confirm with the user before calling — ' +
|
|
711
|
-
'changing the mission alters the daemon\'s autonomous behavior on every subsequent task.',
|
|
712
|
-
{
|
|
713
|
-
instanceId: z.string().min(1).describe('Instance id'),
|
|
714
|
-
mission: z.string().min(1).max(4000).describe('Free-text instruction the daemon carries forward'),
|
|
715
|
-
},
|
|
716
|
-
async ({ instanceId, mission }) => callDaemonTool(instanceId, 'agent_set_mission', { mission })
|
|
717
|
-
);
|
|
718
|
-
|
|
719
|
-
// ── Tool: destroy app (guarded) ──────────────────────────────────────────
|
|
720
|
-
server.tool(
|
|
721
|
-
'zibby_destroy_app',
|
|
722
|
-
'PERMANENTLY destroy a hosted app instance: stops the Fargate task, detaches EFS, revokes the agent-ops MCP token. ' +
|
|
723
|
-
'DESTRUCTIVE — the agent MUST confirm with the user and pass confirm:true.',
|
|
724
|
-
{
|
|
725
|
-
instanceId: z.string().min(1).describe('Instance id to destroy'),
|
|
726
|
-
confirm: z.literal(true).describe('Must be true. Set only after user has explicitly authorized destroying this instance.'),
|
|
727
|
-
keepData: z.boolean().optional().default(false).describe('If true, snapshots the EFS before detach so a future redeploy can restore.'),
|
|
728
|
-
},
|
|
729
|
-
async ({ instanceId, keepData }) => {
|
|
730
|
-
const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}`, {
|
|
731
|
-
method: 'DELETE',
|
|
732
|
-
body: { keepData },
|
|
733
|
-
});
|
|
734
|
-
if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
|
|
735
|
-
if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Destroy failed (${res.status})` }] };
|
|
736
|
-
return jsonResult(res.body);
|
|
737
|
-
}
|
|
738
|
-
);
|
|
739
|
-
|
|
740
436
|
// ── Connect ─────────────────────────────────────────────────────────────
|
|
741
437
|
|
|
742
438
|
await server.connect(new StdioServerTransport());
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/mcp-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Zibby
|
|
3
|
+
"version": "0.3.1",
|
|
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": {
|