@zibby/mcp-cli 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/index.js +240 -0
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -500,6 +500,246 @@ server.tool(
500
500
  }
501
501
  );
502
502
 
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
+ // /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
+ );
571
+
572
+ // ── Tool: list marketplace catalog ──────────────────────────────────────
573
+ server.tool(
574
+ 'zibby_list_app_catalog',
575
+ 'List every app available in the Zibby marketplace. Returns tile metadata (appType, displayName, ' +
576
+ 'tagline, category, version, ratingAvg, installCount). Public — no Zibby login required.',
577
+ {},
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
+ }
584
+ );
585
+
586
+ // ── Tool: list apps ──────────────────────────────────────────────────────
587
+ server.tool(
588
+ 'zibby_list_apps',
589
+ 'List hosted app instances in a project. Returns each instance\'s id, template, status, and MCP endpoint URL.',
590
+ {
591
+ projectId: z.string().min(1).describe('Zibby project to scope to'),
592
+ },
593
+ async ({ projectId }) => {
594
+ const res = await callAppsAPI(`?projectId=${encodeURIComponent(projectId)}`);
595
+ if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
596
+ if (!res.ok) return { isError: true, content: [{ type: 'text', text: `List failed (${res.status})` }] };
597
+ return jsonResult(res.body);
598
+ }
599
+ );
600
+
601
+ // ── Tool: app status ─────────────────────────────────────────────────────
602
+ server.tool(
603
+ 'zibby_app_status',
604
+ 'Show detailed status of one hosted app instance: task state, sidecar version, agent-ops heartbeat, last incident.',
605
+ {
606
+ instanceId: z.string().min(1).describe('Instance id (from zibby_list_apps or zibby_deploy_app)'),
607
+ },
608
+ async ({ instanceId }) => {
609
+ const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}`);
610
+ if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
611
+ if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Status failed (${res.status})` }] };
612
+ return jsonResult(res.body);
613
+ }
614
+ );
615
+
616
+ // ── Tool: app logs ───────────────────────────────────────────────────────
617
+ server.tool(
618
+ 'zibby_app_logs',
619
+ '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.',
620
+ {
621
+ instanceId: z.string().min(1).describe('Instance id'),
622
+ container: z.enum(['app', 'agent-ops']).optional().describe('Which container; defaults to interleaved'),
623
+ lines: z.number().int().min(1).max(5000).optional().default(200).describe('How many recent lines'),
624
+ },
625
+ async ({ instanceId, container, lines }) => {
626
+ const qs = new URLSearchParams();
627
+ if (container) qs.set('container', container);
628
+ if (lines) qs.set('lines', String(lines));
629
+ const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}/logs?${qs.toString()}`);
630
+ if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
631
+ if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Logs failed (${res.status})` }] };
632
+ return textResult(typeof res.body === 'string' ? res.body : JSON.stringify(res.body, null, 2));
633
+ }
634
+ );
635
+
636
+ // ── Daemon MCP proxy ─────────────────────────────────────────────────────
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
+
669
+ server.tool(
670
+ 'zibby_app_history',
671
+ 'Return the recent run history for an instance: each prior task invocation, exit status, and elapsed time. ' +
672
+ 'Useful for "did the nightly health-check pass?" type questions.',
673
+ {
674
+ instanceId: z.string().min(1).describe('Instance id'),
675
+ limit: z.number().int().min(1).max(100).optional().default(20),
676
+ },
677
+ async ({ instanceId, limit }) => callDaemonTool(instanceId, 'agent_history', { limit })
678
+ );
679
+
680
+ server.tool(
681
+ 'zibby_app_list_tasks',
682
+ 'List scheduled tasks configured on an instance (hourly health checks, weekly upgrades, custom cron jobs ' +
683
+ 'the user added). Returns name, cron expression, prompt template.',
684
+ {
685
+ instanceId: z.string().min(1).describe('Instance id'),
686
+ },
687
+ async ({ instanceId }) => callDaemonTool(instanceId, 'agent_list_tasks', {})
688
+ );
689
+
690
+ server.tool(
691
+ 'zibby_app_run_task_now',
692
+ 'Trigger an existing scheduled task to run immediately, out-of-cron. The task name comes from ' +
693
+ 'zibby_app_list_tasks. The daemon kicks off the LLM run async; poll zibby_app_history to see the result.',
694
+ {
695
+ instanceId: z.string().min(1).describe('Instance id'),
696
+ taskName: z.string().min(1).describe('Task name from zibby_app_list_tasks'),
697
+ },
698
+ async ({ instanceId, taskName }) => callDaemonTool(instanceId, 'agent_run_now', { name: taskName })
699
+ );
700
+
701
+ server.tool(
702
+ 'zibby_app_get_mission',
703
+ 'Read the instance\'s persistent "mission" — a free-text instruction the agent-ops daemon ' +
704
+ 'carries across every scheduled task run (e.g. "keep n8n on the latest LTS, never auto-major-upgrade").',
705
+ {
706
+ instanceId: z.string().min(1).describe('Instance id'),
707
+ },
708
+ async ({ instanceId }) => callDaemonTool(instanceId, 'agent_get_mission', {})
709
+ );
710
+
711
+ server.tool(
712
+ 'zibby_app_set_mission',
713
+ 'Set the instance\'s persistent mission. Replaces the prior value. Confirm with the user before calling — ' +
714
+ 'changing the mission alters the daemon\'s autonomous behavior on every subsequent task.',
715
+ {
716
+ instanceId: z.string().min(1).describe('Instance id'),
717
+ mission: z.string().min(1).max(4000).describe('Free-text instruction the daemon carries forward'),
718
+ },
719
+ async ({ instanceId, mission }) => callDaemonTool(instanceId, 'agent_set_mission', { mission })
720
+ );
721
+
722
+ // ── Tool: destroy app (guarded) ──────────────────────────────────────────
723
+ server.tool(
724
+ 'zibby_destroy_app',
725
+ 'PERMANENTLY destroy a hosted app instance: stops the Fargate task, detaches EFS, revokes the agent-ops MCP token. ' +
726
+ 'DESTRUCTIVE — the agent MUST confirm with the user and pass confirm:true.',
727
+ {
728
+ instanceId: z.string().min(1).describe('Instance id to destroy'),
729
+ confirm: z.literal(true).describe('Must be true. Set only after user has explicitly authorized destroying this instance.'),
730
+ keepData: z.boolean().optional().default(false).describe('If true, snapshots the EFS before detach so a future redeploy can restore.'),
731
+ },
732
+ async ({ instanceId, keepData }) => {
733
+ const res = await callAppsAPI(`/${encodeURIComponent(instanceId)}`, {
734
+ method: 'DELETE',
735
+ body: { keepData },
736
+ });
737
+ if (res.error) return { isError: true, content: [{ type: 'text', text: `Network: ${res.error}` }] };
738
+ if (!res.ok) return { isError: true, content: [{ type: 'text', text: `Destroy failed (${res.status})` }] };
739
+ return jsonResult(res.body);
740
+ }
741
+ );
742
+
503
743
  // ── Connect ─────────────────────────────────────────────────────────────
504
744
 
505
745
  await server.connect(new StdioServerTransport());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/mcp-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Zibby CLI MCP Server — expose Zibby workflow deploy/run/debug to AI agents (Claude, Cursor, Codex, Gemini)",
5
5
  "type": "module",
6
6
  "main": "index.js",