@fruition/fcp-mcp-server 1.30.0 → 1.32.0

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 CHANGED
@@ -33,6 +33,22 @@ MCP (Model Context Protocol) server that gives Claude Code direct access to the
33
33
  | `fcp_get_certificate` | Single certificate detail (issuer, SANs, expiry) |
34
34
  | `fcp_scan_certificates` | Trigger a cert-monitor scan run |
35
35
 
36
+ ### Cluster Gateway Tools
37
+
38
+ | Tool | Description |
39
+ |------|-------------|
40
+ | `fcp_db_query` | Run a read-only SQL query (SELECT/SHOW/EXPLAIN/DESCRIBE) against a site's read-only DB replica |
41
+ | `fcp_cluster_http` | GET/HEAD an in-cluster Kubernetes Service URL through FCP (devs firewalled off the cluster) |
42
+
43
+ ### Cluster Safe-Edit Action Tools
44
+
45
+ Structural, audited mutations (operator tier). The caller supplies ONLY a site (+ optional revision); the target deployment is resolved from the site's stack server-side — no deployment name or raw command is ever accepted. Admin-gated REST routes, `CLUSTER_GATEWAY_DISABLED` kill-switch, prod-cluster Slack alert.
46
+
47
+ | Tool | Description |
48
+ |------|-------------|
49
+ | `fcp_restart_varnish` | Restart a site's varnish deployment (rollout restart) |
50
+ | `fcp_rollback_web` | Roll back a site's web (nginx + php-fpm) deployment (rollout undo), optional `to_revision` |
51
+
36
52
  ### Tasker Job Control Tools
37
53
 
38
54
  | Tool | Description |
package/dist/index.d.ts CHANGED
@@ -416,6 +416,20 @@ export declare class FCPClient {
416
416
  enabled?: boolean;
417
417
  created_by?: string;
418
418
  }): Promise<any>;
419
+ clusterDbQuery(input: {
420
+ site?: number | string;
421
+ domain?: string;
422
+ sql: string;
423
+ limit?: number;
424
+ }): Promise<any>;
425
+ clusterHttp(input: {
426
+ cluster: string;
427
+ namespace: string;
428
+ service: string;
429
+ port: number;
430
+ path?: string;
431
+ method?: string;
432
+ }): Promise<any>;
419
433
  listCertificates(filters?: {
420
434
  status?: string;
421
435
  cluster?: string;
@@ -451,6 +465,55 @@ export declare class FCPClient {
451
465
  reason?: string;
452
466
  ttl_minutes?: number;
453
467
  }): Promise<any>;
468
+ clusterPods(input: {
469
+ site?: string;
470
+ domain?: string;
471
+ cluster?: string;
472
+ namespace?: string;
473
+ labelSelector?: string;
474
+ }): Promise<any>;
475
+ clusterLogs(input: {
476
+ pod: string;
477
+ site?: string;
478
+ domain?: string;
479
+ cluster?: string;
480
+ namespace?: string;
481
+ }): Promise<any>;
482
+ clusterIngress(input: {
483
+ site?: string;
484
+ domain?: string;
485
+ cluster?: string;
486
+ namespace?: string;
487
+ }): Promise<any>;
488
+ clusterDeployments(input: {
489
+ site?: string;
490
+ domain?: string;
491
+ cluster?: string;
492
+ namespace?: string;
493
+ }): Promise<any>;
494
+ clusterEvents(input: {
495
+ site?: string;
496
+ domain?: string;
497
+ cluster?: string;
498
+ namespace?: string;
499
+ }): Promise<any>;
500
+ clusterDescribe(input: {
501
+ kind: string;
502
+ name: string;
503
+ site?: string;
504
+ domain?: string;
505
+ cluster?: string;
506
+ namespace?: string;
507
+ }): Promise<any>;
508
+ restartVarnish(input: {
509
+ site?: string;
510
+ domain?: string;
511
+ }): Promise<any>;
512
+ rollbackWeb(input: {
513
+ site?: string;
514
+ domain?: string;
515
+ to_revision?: number;
516
+ }): Promise<any>;
454
517
  backupListSites(): Promise<any>;
455
518
  backupGetConfig(): Promise<any>;
456
519
  backupListEligible(): Promise<any>;
package/dist/index.js CHANGED
@@ -167,7 +167,19 @@ const TOOL_PERMISSIONS = {
167
167
  fcp_trusted_ip_list_ranges: 'viewer',
168
168
  fcp_trusted_ip_export: 'viewer',
169
169
  fcp_list_freezes: 'viewer',
170
+ fcp_db_query: 'viewer',
171
+ fcp_cluster_http: 'viewer',
170
172
  fcp_list_certificates: 'viewer',
173
+ fcp_cluster_pods: 'viewer',
174
+ fcp_cluster_logs: 'viewer',
175
+ fcp_cluster_ingress: 'viewer',
176
+ fcp_cluster_deployments: 'viewer',
177
+ fcp_cluster_events: 'viewer',
178
+ fcp_cluster_describe: 'viewer',
179
+ // Cluster Safe-Edit Actions (Phase 2) — operator tier (developer tier is a
180
+ // separate cross-cutting follow-up).
181
+ fcp_restart_varnish: 'operator',
182
+ fcp_rollback_web: 'operator',
171
183
  fcp_dns_current: 'viewer',
172
184
  fcp_dns_history: 'viewer',
173
185
  fcp_list_deployments: 'viewer',
@@ -920,6 +932,21 @@ export class FCPClient {
920
932
  });
921
933
  }
922
934
  // ============================================================================
935
+ // Cluster DB + HTTP Methods
936
+ // ============================================================================
937
+ async clusterDbQuery(input) {
938
+ return this.fetch('/api/cluster/db-query', {
939
+ method: 'POST',
940
+ body: JSON.stringify(input),
941
+ });
942
+ }
943
+ async clusterHttp(input) {
944
+ return this.fetch('/api/cluster/http', {
945
+ method: 'POST',
946
+ body: JSON.stringify(input),
947
+ });
948
+ }
949
+ // ============================================================================
923
950
  // Certificate Methods
924
951
  // ============================================================================
925
952
  async listCertificates(filters) {
@@ -993,6 +1020,109 @@ export class FCPClient {
993
1020
  return this.fetch(path, { method: 'POST', body: JSON.stringify(body) });
994
1021
  }
995
1022
  // ============================================================================
1023
+ // Cluster Info (read-only) Methods
1024
+ // ============================================================================
1025
+ async clusterPods(input) {
1026
+ const p = new URLSearchParams();
1027
+ if (input.site)
1028
+ p.append('site', input.site);
1029
+ if (input.domain)
1030
+ p.append('domain', input.domain);
1031
+ if (input.cluster)
1032
+ p.append('cluster', input.cluster);
1033
+ if (input.namespace)
1034
+ p.append('namespace', input.namespace);
1035
+ if (input.labelSelector)
1036
+ p.append('labelSelector', input.labelSelector);
1037
+ const qs = p.toString();
1038
+ return this.fetch(`/api/cluster/info/pods${qs ? `?${qs}` : ''}`);
1039
+ }
1040
+ async clusterLogs(input) {
1041
+ const p = new URLSearchParams();
1042
+ p.append('pod', input.pod);
1043
+ if (input.site)
1044
+ p.append('site', input.site);
1045
+ if (input.domain)
1046
+ p.append('domain', input.domain);
1047
+ if (input.cluster)
1048
+ p.append('cluster', input.cluster);
1049
+ if (input.namespace)
1050
+ p.append('namespace', input.namespace);
1051
+ return this.fetch(`/api/cluster/info/logs?${p.toString()}`);
1052
+ }
1053
+ async clusterIngress(input) {
1054
+ const p = new URLSearchParams();
1055
+ if (input.site)
1056
+ p.append('site', input.site);
1057
+ if (input.domain)
1058
+ p.append('domain', input.domain);
1059
+ if (input.cluster)
1060
+ p.append('cluster', input.cluster);
1061
+ if (input.namespace)
1062
+ p.append('namespace', input.namespace);
1063
+ const qs = p.toString();
1064
+ return this.fetch(`/api/cluster/info/ingress${qs ? `?${qs}` : ''}`);
1065
+ }
1066
+ async clusterDeployments(input) {
1067
+ const p = new URLSearchParams();
1068
+ if (input.site)
1069
+ p.append('site', input.site);
1070
+ if (input.domain)
1071
+ p.append('domain', input.domain);
1072
+ if (input.cluster)
1073
+ p.append('cluster', input.cluster);
1074
+ if (input.namespace)
1075
+ p.append('namespace', input.namespace);
1076
+ const qs = p.toString();
1077
+ return this.fetch(`/api/cluster/info/deployments${qs ? `?${qs}` : ''}`);
1078
+ }
1079
+ async clusterEvents(input) {
1080
+ const p = new URLSearchParams();
1081
+ if (input.site)
1082
+ p.append('site', input.site);
1083
+ if (input.domain)
1084
+ p.append('domain', input.domain);
1085
+ if (input.cluster)
1086
+ p.append('cluster', input.cluster);
1087
+ if (input.namespace)
1088
+ p.append('namespace', input.namespace);
1089
+ const qs = p.toString();
1090
+ return this.fetch(`/api/cluster/info/events${qs ? `?${qs}` : ''}`);
1091
+ }
1092
+ async clusterDescribe(input) {
1093
+ const p = new URLSearchParams();
1094
+ p.append('kind', input.kind);
1095
+ p.append('name', input.name);
1096
+ if (input.site)
1097
+ p.append('site', input.site);
1098
+ if (input.domain)
1099
+ p.append('domain', input.domain);
1100
+ if (input.cluster)
1101
+ p.append('cluster', input.cluster);
1102
+ if (input.namespace)
1103
+ p.append('namespace', input.namespace);
1104
+ return this.fetch(`/api/cluster/info/describe?${p.toString()}`);
1105
+ }
1106
+ // ============================================================================
1107
+ // Cluster Safe-Edit Actions (FCP Cluster Gateway, Phase 2)
1108
+ // ============================================================================
1109
+ async restartVarnish(input) {
1110
+ return this.fetch('/api/cluster/actions/restart-varnish', {
1111
+ method: 'POST',
1112
+ body: JSON.stringify({ site: input.site, domain: input.domain }),
1113
+ });
1114
+ }
1115
+ async rollbackWeb(input) {
1116
+ return this.fetch('/api/cluster/actions/rollback-web', {
1117
+ method: 'POST',
1118
+ body: JSON.stringify({
1119
+ site: input.site,
1120
+ domain: input.domain,
1121
+ to_revision: input.to_revision,
1122
+ }),
1123
+ });
1124
+ }
1125
+ // ============================================================================
996
1126
  // Backup Management Methods
997
1127
  // ============================================================================
998
1128
  async backupListSites() {
@@ -3450,6 +3580,39 @@ const TOOLS = [
3450
3580
  },
3451
3581
  },
3452
3582
  // ============================================================================
3583
+ // Cluster DB + HTTP Tools
3584
+ // ============================================================================
3585
+ {
3586
+ name: 'fcp_db_query',
3587
+ description: 'Run a READ-ONLY SQL query against a site\'s read-only DB replica. Provide either site (numeric id) or domain, plus a single SELECT/SHOW/EXPLAIN/DESCRIBE statement. Writes/DDL/multi-statement are rejected. Rows are capped (default 200). Use to inspect site data without cluster access.',
3588
+ inputSchema: {
3589
+ type: 'object',
3590
+ properties: {
3591
+ site: { type: 'number', description: 'Site id (provide site OR domain)' },
3592
+ domain: { type: 'string', description: 'Site domain (provide site OR domain)' },
3593
+ sql: { type: 'string', description: 'Single read-only statement (SELECT/SHOW/EXPLAIN/DESCRIBE)' },
3594
+ limit: { type: 'number', description: 'Max rows to return (default 200, max 1000)' },
3595
+ },
3596
+ required: ['sql'],
3597
+ },
3598
+ },
3599
+ {
3600
+ name: 'fcp_cluster_http',
3601
+ description: 'Fetch an in-cluster Kubernetes Service URL through FCP (devs are firewalled off the cluster). Builds http://<service>.<namespace>:<port>/<path>. Only GET/HEAD allowed; response body is size-capped. Use to hit health/metrics/internal endpoints.',
3602
+ inputSchema: {
3603
+ type: 'object',
3604
+ properties: {
3605
+ cluster: { type: 'string', description: 'Target cluster name (recorded for audit)' },
3606
+ namespace: { type: 'string', description: 'Kubernetes namespace' },
3607
+ service: { type: 'string', description: 'Kubernetes Service name' },
3608
+ port: { type: 'number', description: 'Service port' },
3609
+ path: { type: 'string', description: 'Request path (default /)' },
3610
+ method: { type: 'string', enum: ['GET', 'HEAD'], description: 'HTTP method (default GET)' },
3611
+ },
3612
+ required: ['cluster', 'namespace', 'service', 'port'],
3613
+ },
3614
+ },
3615
+ // ============================================================================
3453
3616
  // Certificate Tools
3454
3617
  // ============================================================================
3455
3618
  {
@@ -3548,6 +3711,122 @@ const TOOLS = [
3548
3711
  },
3549
3712
  },
3550
3713
  // ============================================================================
3714
+ // Cluster Info (read-only) Tools
3715
+ // ============================================================================
3716
+ {
3717
+ name: 'fcp_cluster_pods',
3718
+ description: 'List pods in a namespace (read-only cluster gateway for firewalled devs). Target a namespace by EITHER a site (\"site\"/\"domain\") OR an explicit \"cluster\"+\"namespace\" pair. Optional labelSelector.',
3719
+ inputSchema: {
3720
+ type: 'object',
3721
+ properties: {
3722
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace)' },
3723
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3724
+ cluster: { type: 'string', description: 'Explicit cluster name (with namespace)' },
3725
+ namespace: { type: 'string', description: 'Explicit namespace (with cluster)' },
3726
+ labelSelector: { type: 'string', description: 'Optional label selector (e.g. app=web)' },
3727
+ },
3728
+ },
3729
+ },
3730
+ {
3731
+ name: 'fcp_cluster_logs',
3732
+ description: 'Tail logs (last 1000 lines) for a pod (read-only cluster gateway). Requires pod. Target a namespace by EITHER a site (\"site\"/\"domain\") OR an explicit \"cluster\"+\"namespace\" pair.',
3733
+ inputSchema: {
3734
+ type: 'object',
3735
+ properties: {
3736
+ pod: { type: 'string', description: 'Pod name to read logs from' },
3737
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace)' },
3738
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3739
+ cluster: { type: 'string', description: 'Explicit cluster name (with namespace)' },
3740
+ namespace: { type: 'string', description: 'Explicit namespace (with cluster)' },
3741
+ },
3742
+ required: ['pod'],
3743
+ },
3744
+ },
3745
+ {
3746
+ name: 'fcp_cluster_ingress',
3747
+ description: 'List ingresses in a namespace (read-only cluster gateway). Target a namespace by EITHER a site (\"site\"/\"domain\") OR an explicit \"cluster\"+\"namespace\" pair.',
3748
+ inputSchema: {
3749
+ type: 'object',
3750
+ properties: {
3751
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace)' },
3752
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3753
+ cluster: { type: 'string', description: 'Explicit cluster name (with namespace)' },
3754
+ namespace: { type: 'string', description: 'Explicit namespace (with cluster)' },
3755
+ },
3756
+ },
3757
+ },
3758
+ {
3759
+ name: 'fcp_cluster_deployments',
3760
+ description: 'List deployments in a namespace (read-only cluster gateway). Target a namespace by EITHER a site (\"site\"/\"domain\") OR an explicit \"cluster\"+\"namespace\" pair.',
3761
+ inputSchema: {
3762
+ type: 'object',
3763
+ properties: {
3764
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace)' },
3765
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3766
+ cluster: { type: 'string', description: 'Explicit cluster name (with namespace)' },
3767
+ namespace: { type: 'string', description: 'Explicit namespace (with cluster)' },
3768
+ },
3769
+ },
3770
+ },
3771
+ {
3772
+ name: 'fcp_cluster_events',
3773
+ description: 'List namespace events sorted by lastTimestamp (read-only cluster gateway). Target a namespace by EITHER a site (\"site\"/\"domain\") OR an explicit \"cluster\"+\"namespace\" pair.',
3774
+ inputSchema: {
3775
+ type: 'object',
3776
+ properties: {
3777
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace)' },
3778
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3779
+ cluster: { type: 'string', description: 'Explicit cluster name (with namespace)' },
3780
+ namespace: { type: 'string', description: 'Explicit namespace (with cluster)' },
3781
+ },
3782
+ },
3783
+ },
3784
+ {
3785
+ name: 'fcp_cluster_describe',
3786
+ description: 'kubectl describe <kind> <name> in a namespace (read-only cluster gateway). Requires kind + name. Target a namespace by EITHER a site (\"site\"/\"domain\") OR an explicit \"cluster\"+\"namespace\" pair.',
3787
+ inputSchema: {
3788
+ type: 'object',
3789
+ properties: {
3790
+ kind: { type: 'string', description: 'Resource kind (e.g. pod, deployment, ingress)' },
3791
+ name: { type: 'string', description: 'Resource name to describe' },
3792
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace)' },
3793
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3794
+ cluster: { type: 'string', description: 'Explicit cluster name (with namespace)' },
3795
+ namespace: { type: 'string', description: 'Explicit namespace (with cluster)' },
3796
+ },
3797
+ required: ['kind', 'name'],
3798
+ },
3799
+ },
3800
+ // ============================================================================
3801
+ // Cluster Safe-Edit Actions (FCP Cluster Gateway, Phase 2)
3802
+ // Operator-tier, structurally-allowlisted: the caller supplies ONLY a site
3803
+ // (+ optional revision). No deployment name, kind, or raw command is ever
3804
+ // accepted — the deployment is resolved from the site's stack server-side.
3805
+ // ============================================================================
3806
+ {
3807
+ name: 'fcp_restart_varnish',
3808
+ description: "Safe-edit: restart a site's varnish deployment (rollout restart). Target the site by \"site\" (website id or domain) or \"domain\". The varnish deployment is resolved structurally from the site's stack — you do NOT (and cannot) specify a deployment name or command. Operator tier; admin-gated server-side. Honors the cluster-gateway kill-switch.",
3809
+ inputSchema: {
3810
+ type: 'object',
3811
+ properties: {
3812
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace+stack)' },
3813
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3814
+ },
3815
+ },
3816
+ },
3817
+ {
3818
+ name: 'fcp_rollback_web',
3819
+ description: "Safe-edit: roll back a site's web (nginx + php-fpm) deployment (rollout undo), optionally to a specific revision. Target the site by \"site\" (website id or domain) or \"domain\"; optional \"to_revision\" (positive integer). The web deployment is resolved structurally from the site's stack — you do NOT (and cannot) specify a deployment name or command. Operator tier; admin-gated server-side. Honors the cluster-gateway kill-switch.",
3820
+ inputSchema: {
3821
+ type: 'object',
3822
+ properties: {
3823
+ site: { type: 'string', description: 'Website id or domain (resolves cluster+namespace+stack)' },
3824
+ domain: { type: 'string', description: 'Alias for site: a domain to resolve' },
3825
+ to_revision: { type: 'number', description: 'Optional revision to roll back to (positive integer)' },
3826
+ },
3827
+ },
3828
+ },
3829
+ // ============================================================================
3551
3830
  // Backup Management Tools
3552
3831
  // ============================================================================
3553
3832
  {
@@ -5470,6 +5749,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5470
5749
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5471
5750
  }
5472
5751
  // ============================================================================
5752
+ // Cluster DB + HTTP Handlers
5753
+ // ============================================================================
5754
+ case 'fcp_db_query': {
5755
+ const result = await client.clusterDbQuery(args);
5756
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5757
+ }
5758
+ case 'fcp_cluster_http': {
5759
+ const result = await client.clusterHttp(args);
5760
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5761
+ }
5762
+ // ============================================================================
5473
5763
  // Certificate Handlers
5474
5764
  // ============================================================================
5475
5765
  case 'fcp_list_certificates': {
@@ -5518,6 +5808,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5518
5808
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5519
5809
  }
5520
5810
  // ============================================================================
5811
+ // Cluster Info (read-only) Handlers
5812
+ // ============================================================================
5813
+ case 'fcp_cluster_pods': {
5814
+ const result = await client.clusterPods(args);
5815
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5816
+ }
5817
+ case 'fcp_cluster_logs': {
5818
+ const result = await client.clusterLogs(args);
5819
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5820
+ }
5821
+ case 'fcp_cluster_ingress': {
5822
+ const result = await client.clusterIngress(args);
5823
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5824
+ }
5825
+ case 'fcp_cluster_deployments': {
5826
+ const result = await client.clusterDeployments(args);
5827
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5828
+ }
5829
+ case 'fcp_cluster_events': {
5830
+ const result = await client.clusterEvents(args);
5831
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5832
+ }
5833
+ case 'fcp_cluster_describe': {
5834
+ const result = await client.clusterDescribe(args);
5835
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5836
+ }
5837
+ // ============================================================================
5838
+ // Cluster Safe-Edit Action Handlers (FCP Cluster Gateway, Phase 2)
5839
+ // ============================================================================
5840
+ case 'fcp_restart_varnish': {
5841
+ const result = await client.restartVarnish(args);
5842
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5843
+ }
5844
+ case 'fcp_rollback_web': {
5845
+ const result = await client.rollbackWeb(args);
5846
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5847
+ }
5848
+ // ============================================================================
5521
5849
  // Backup Management Handlers
5522
5850
  // ============================================================================
5523
5851
  case 'fcp_backup_list_sites': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fruition/fcp-mcp-server",
3
- "version": "1.30.0",
3
+ "version": "1.32.0",
4
4
  "description": "MCP Server for FCP Launch Coordination System - enables Claude Code to interact with FCP launches and track development time",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",