@zibby/mcp-cli 0.3.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.
Files changed (2) hide show
  1. package/index.js +231 -0
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -433,6 +433,237 @@ server.tool(
433
433
  }
434
434
  );
435
435
 
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.
439
+
440
+ // ── Tool: list app templates (the catalog). Public endpoint — no auth.
441
+ server.tool(
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.',
444
+ {},
445
+ async () => cliResult(await runCli(['app', 'templates']))
446
+ );
447
+
448
+ // ── Tool: list deployed apps. Project-scoped when projectId is given;
449
+ // otherwise lists every instance under the caller's account.
450
+ server.tool(
451
+ 'zibby_list_apps',
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.',
453
+ {
454
+ projectId: z.string().min(1).optional()
455
+ .describe('Optional project ID — filter to one project\'s instances'),
456
+ },
457
+ async ({ projectId }) => {
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 }));
469
+ }
470
+ );
471
+
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.
475
+ server.tool(
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.',
478
+ {
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).'),
485
+ },
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 } }));
511
+ }
512
+ );
513
+
514
+ // ── Tool: status of a single instance — live ECS state, public URL,
515
+ // resources, app version, project.
516
+ server.tool(
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?".',
519
+ {
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)'),
523
+ },
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 }));
531
+ }
532
+ );
533
+
534
+ // ── Tool: tail recent logs for a single instance. One-shot snapshot
535
+ // (no streaming over MCP — agents can call again for fresh batches).
536
+ server.tool(
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.',
539
+ {
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)'),
544
+ },
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
+ }
555
+ );
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.
562
+ server.tool(
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.',
565
+ {
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.'),
570
+ },
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
+ }
581
+ );
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).
586
+ server.tool(
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.',
589
+ {
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.'),
593
+ },
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
+ }
602
+ );
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.
612
+ server.tool(
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.',
615
+ {
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.'),
618
+ },
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
+ }
624
+ );
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.
632
+ server.tool(
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".',
635
+ {
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.'),
637
+ },
638
+ async ({ token }) => {
639
+ const args = ['creds', 'set', 'openai', token];
640
+ return cliResult(await runCli(args));
641
+ }
642
+ );
643
+
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.
650
+ server.tool(
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.',
653
+ {
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)'),
656
+ },
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 }));
664
+ }
665
+ );
666
+
436
667
  // ── Connect ─────────────────────────────────────────────────────────────
437
668
 
438
669
  await server.connect(new StdioServerTransport());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/mcp-cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
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",