@fruition/fcp-mcp-server 1.5.0 → 1.6.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.
Files changed (2) hide show
  1. package/dist/index.js +1490 -94
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45,6 +45,12 @@ const UNROO_API_KEY = process.env.UNROO_API_KEY || '';
45
45
  const USE_FCP_UNROO_PROXY = !UNROO_API_KEY;
46
46
  // Helper to check if Unroo functionality is available (either mode)
47
47
  const UNROO_AVAILABLE = UNROO_API_KEY || (USE_FCP_UNROO_PROXY && FCP_API_TOKEN);
48
+ // Unique instance ID for this MCP server process
49
+ // Combines hostname + PID + startup timestamp to ensure uniqueness across:
50
+ // - Multiple machines (hostname)
51
+ // - Multiple processes on same machine (PID)
52
+ // - Process restarts (timestamp)
53
+ const INSTANCE_ID = `${process.env.HOSTNAME || 'local'}-${process.pid}-${Date.now()}`;
48
54
  let currentProject = null;
49
55
  /**
50
56
  * Detect the current git remote URL
@@ -173,12 +179,48 @@ class FCPClient {
173
179
  async getLaunch(id) {
174
180
  return this.fetch(`/api/launches/${id}`);
175
181
  }
182
+ async createChecklistItem(launchId, input) {
183
+ return this.fetch(`/api/launches/${launchId}/checklist`, {
184
+ method: 'POST',
185
+ body: JSON.stringify(input),
186
+ });
187
+ }
176
188
  async updateChecklistItem(launchId, itemId, updates) {
177
189
  return this.fetch(`/api/launches/${launchId}/checklist/${itemId}`, {
178
190
  method: 'PUT',
179
191
  body: JSON.stringify(updates),
180
192
  });
181
193
  }
194
+ async deleteChecklistItem(launchId, itemId) {
195
+ return this.fetch(`/api/launches/${launchId}/checklist/${itemId}`, {
196
+ method: 'DELETE',
197
+ });
198
+ }
199
+ async createLaunch(input) {
200
+ return this.fetch('/api/launches', {
201
+ method: 'POST',
202
+ body: JSON.stringify(input),
203
+ });
204
+ }
205
+ async updateLaunch(id, updates) {
206
+ return this.fetch(`/api/launches/${id}`, {
207
+ method: 'PUT',
208
+ body: JSON.stringify(updates),
209
+ });
210
+ }
211
+ async deleteLaunch(id) {
212
+ return this.fetch(`/api/launches/${id}`, {
213
+ method: 'DELETE',
214
+ });
215
+ }
216
+ async getSuccessFactors(launchId, itemId) {
217
+ return this.fetch(`/api/launches/${launchId}/checklist/${itemId}/success-factors`);
218
+ }
219
+ async validateChecklistItem(launchId, itemId) {
220
+ return this.fetch(`/api/launches/${launchId}/checklist/${itemId}/validate`, {
221
+ method: 'POST',
222
+ });
223
+ }
182
224
  async getClaudeMd(launchId) {
183
225
  return this.fetch(`/api/launches/${launchId}/claude-md`);
184
226
  }
@@ -188,6 +230,105 @@ class FCPClient {
188
230
  body: JSON.stringify({ content }),
189
231
  });
190
232
  }
233
+ // FileSync methods
234
+ async listFileSyncConfigs(filters) {
235
+ const params = new URLSearchParams();
236
+ if (filters?.enabled !== undefined)
237
+ params.set('enabled', String(filters.enabled));
238
+ if (filters?.sync_direction)
239
+ params.set('sync_direction', filters.sync_direction);
240
+ const query = params.toString();
241
+ return this.fetch(`/api/filesync/configs${query ? `?${query}` : ''}`);
242
+ }
243
+ async getFileSyncJobs(filters) {
244
+ const params = new URLSearchParams();
245
+ if (filters?.config_id)
246
+ params.set('config_id', String(filters.config_id));
247
+ if (filters?.status)
248
+ params.set('status', filters.status);
249
+ if (filters?.limit)
250
+ params.set('limit', String(filters.limit));
251
+ const query = params.toString();
252
+ return this.fetch(`/api/filesync/jobs${query ? `?${query}` : ''}`);
253
+ }
254
+ async startFileSync(input) {
255
+ return this.fetch('/api/filesync/start', {
256
+ method: 'POST',
257
+ body: JSON.stringify(input),
258
+ });
259
+ }
260
+ async cancelFileSync(jobId) {
261
+ return this.fetch(`/api/filesync/cancel/${encodeURIComponent(jobId)}`, {
262
+ method: 'POST',
263
+ });
264
+ }
265
+ async getFileSyncConfirmation(configId) {
266
+ return this.fetch('/api/filesync/confirm', {
267
+ method: 'POST',
268
+ body: JSON.stringify({ config_id: configId }),
269
+ });
270
+ }
271
+ async triggerNucleiScan(websiteId, options) {
272
+ return this.fetch(`/api/sites/${websiteId}/nuclei-scan`, {
273
+ method: 'POST',
274
+ body: JSON.stringify(options || {}),
275
+ }, 30000);
276
+ }
277
+ async getNucleiResults(websiteId, options) {
278
+ // If scan_id provided, get detailed results for that specific scan
279
+ if (options?.scan_id) {
280
+ return this.fetch(`/api/sites/${websiteId}/nuclei-scans/${options.scan_id}`);
281
+ }
282
+ // Otherwise, get scan history list
283
+ return this.fetch(`/api/sites/${websiteId}/nuclei-scans`);
284
+ }
285
+ // Site/Website CRUD Methods
286
+ async listSites(filters) {
287
+ const params = new URLSearchParams();
288
+ if (filters?.account_id)
289
+ params.set('account_id', filters.account_id.toString());
290
+ if (filters?.cms)
291
+ params.set('cms', filters.cms);
292
+ if (filters?.environment)
293
+ params.set('environment', filters.environment);
294
+ if (filters?.fru_hosted !== undefined)
295
+ params.set('fru_hosted', String(filters.fru_hosted));
296
+ if (filters?.retired)
297
+ params.set('retired', filters.retired);
298
+ if (filters?.limit)
299
+ params.set('limit', filters.limit.toString());
300
+ if (filters?.offset)
301
+ params.set('offset', filters.offset.toString());
302
+ const query = params.toString();
303
+ return this.fetch(`/api/sites${query ? `?${query}` : ''}`);
304
+ }
305
+ async searchSites(query, limit) {
306
+ const params = new URLSearchParams({ q: query });
307
+ if (limit)
308
+ params.set('limit', limit.toString());
309
+ return this.fetch(`/api/sites/search?${params.toString()}`);
310
+ }
311
+ async getSite(siteId) {
312
+ return this.fetch(`/api/sites/${siteId}`);
313
+ }
314
+ async createSite(production, staging) {
315
+ return this.fetch('/api/projects/websites/create-with-staging', {
316
+ method: 'POST',
317
+ body: JSON.stringify({ production, staging: staging || [] }),
318
+ });
319
+ }
320
+ async updateSite(siteId, updates) {
321
+ return this.fetch(`/api/sites/${siteId}`, {
322
+ method: 'PUT',
323
+ body: JSON.stringify(updates),
324
+ });
325
+ }
326
+ async deleteSite(siteId, options) {
327
+ return this.fetch(`/api/sites/${siteId}`, {
328
+ method: 'DELETE',
329
+ body: JSON.stringify(options || {}),
330
+ });
331
+ }
191
332
  }
192
333
  // Unroo API Client
193
334
  // Supports two modes:
@@ -287,10 +428,10 @@ class UnrooClient {
287
428
  body: JSON.stringify({ action: 'start', ...input }),
288
429
  });
289
430
  }
290
- async endSession() {
431
+ async endSession(input) {
291
432
  return this.fetch('/api/external/fcp/sessions', {
292
433
  method: 'POST',
293
- body: JSON.stringify({ action: 'end' }),
434
+ body: JSON.stringify({ action: 'end', ...input }),
294
435
  });
295
436
  }
296
437
  async sessionHeartbeat(input) {
@@ -418,7 +559,7 @@ class SessionTracker {
418
559
  const sessionInput = {
419
560
  task_id: this.currentTaskId || undefined,
420
561
  source: 'claude-code-mcp',
421
- machine_id: process.env.HOSTNAME || 'unknown',
562
+ machine_id: INSTANCE_ID, // Unique per MCP server process to prevent session collision
422
563
  };
423
564
  // Add auto-detected project info
424
565
  if (currentProject) {
@@ -459,6 +600,8 @@ class SessionTracker {
459
600
  await this.unrooClient.sessionHeartbeat({
460
601
  tool_calls_delta: this.toolCallCount,
461
602
  activity: this.activities.slice(-10), // Send last 10 activities
603
+ machine_id: INSTANCE_ID, // Required for session key lookup
604
+ repo_name: currentProject ? `${currentProject.github.owner}/${currentProject.github.repo}` : undefined,
462
605
  });
463
606
  this.lastHeartbeat = new Date();
464
607
  this.toolCallCount = 0;
@@ -483,8 +626,11 @@ class SessionTracker {
483
626
  this.heartbeatInterval = null;
484
627
  }
485
628
  try {
486
- // End the session first
487
- const result = await this.unrooClient.endSession();
629
+ // End the session first - include machine_id and repo_name for session key lookup
630
+ const result = await this.unrooClient.endSession({
631
+ machine_id: INSTANCE_ID,
632
+ repo_name: currentProject ? `${currentProject.github.owner}/${currentProject.github.repo}` : undefined,
633
+ });
488
634
  this.sessionActive = false;
489
635
  console.error('[SessionTracker] Session ended');
490
636
  // Log activity summary to the task if we have a current task
@@ -618,9 +764,72 @@ const TOOLS = [
618
764
  required: ['launch_id'],
619
765
  },
620
766
  },
767
+ {
768
+ name: 'fcp_add_checklist_item',
769
+ description: 'Add a new checklist item to a launch. Requires title and category at minimum.',
770
+ inputSchema: {
771
+ type: 'object',
772
+ properties: {
773
+ launch_id: {
774
+ type: 'number',
775
+ description: 'The ID of the launch',
776
+ },
777
+ title: {
778
+ type: 'string',
779
+ description: 'Title of the checklist item',
780
+ },
781
+ category: {
782
+ type: 'string',
783
+ enum: ['pre_launch', 'content', 'technical', 'seo', 'testing', 'dns', 'monitoring', 'post_launch'],
784
+ description: 'Category for the checklist item',
785
+ },
786
+ description: {
787
+ type: 'string',
788
+ description: 'Detailed description of the item',
789
+ },
790
+ role: {
791
+ type: 'string',
792
+ description: 'Team role responsible: webops, devops, dev, seo, client',
793
+ },
794
+ environment: {
795
+ type: 'string',
796
+ description: 'Environment: production, staging, both',
797
+ },
798
+ assigned_to_id: {
799
+ type: 'number',
800
+ description: 'User ID to assign the item to',
801
+ },
802
+ due_date: {
803
+ type: 'string',
804
+ description: 'Due date in ISO format',
805
+ },
806
+ sort_order: {
807
+ type: 'number',
808
+ description: 'Sort order within the category',
809
+ },
810
+ depends_on_item_id: {
811
+ type: 'number',
812
+ description: 'ID of another checklist item this depends on',
813
+ },
814
+ is_required: {
815
+ type: 'boolean',
816
+ description: 'Whether this item is required for launch (default: true)',
817
+ },
818
+ is_blocking: {
819
+ type: 'boolean',
820
+ description: 'Whether this item blocks the launch (default: false)',
821
+ },
822
+ external_link: {
823
+ type: 'string',
824
+ description: 'External URL related to this item',
825
+ },
826
+ },
827
+ required: ['launch_id', 'title', 'category'],
828
+ },
829
+ },
621
830
  {
622
831
  name: 'fcp_update_checklist_item',
623
- description: 'Update the status of a checklist item. Use this to mark items as completed, in_progress, or blocked as you work through tasks.',
832
+ description: 'Update a checklist item. Can change status, title, description, category, assignment, and other fields.',
624
833
  inputSchema: {
625
834
  type: 'object',
626
835
  properties: {
@@ -639,10 +848,85 @@ const TOOLS = [
639
848
  },
640
849
  notes: {
641
850
  type: 'string',
642
- description: 'Optional notes about the status change',
851
+ description: 'Notes about the status change (alias for completion_notes)',
852
+ },
853
+ title: {
854
+ type: 'string',
855
+ description: 'Updated title',
856
+ },
857
+ description: {
858
+ type: 'string',
859
+ description: 'Updated description (null to clear)',
860
+ },
861
+ category: {
862
+ type: 'string',
863
+ enum: ['pre_launch', 'content', 'technical', 'seo', 'testing', 'dns', 'monitoring', 'post_launch'],
864
+ description: 'Updated category',
865
+ },
866
+ role: {
867
+ type: 'string',
868
+ description: 'Updated team role: webops, devops, dev, seo, client',
869
+ },
870
+ environment: {
871
+ type: 'string',
872
+ description: 'Updated environment: production, staging, both',
873
+ },
874
+ assigned_to_id: {
875
+ type: 'number',
876
+ description: 'User ID to assign to (null to unassign)',
877
+ },
878
+ due_date: {
879
+ type: 'string',
880
+ description: 'Updated due date in ISO format (null to clear)',
881
+ },
882
+ sort_order: {
883
+ type: 'number',
884
+ description: 'Updated sort order',
885
+ },
886
+ depends_on_item_id: {
887
+ type: 'number',
888
+ description: 'Updated dependency item ID (null to clear)',
889
+ },
890
+ is_required: {
891
+ type: 'boolean',
892
+ description: 'Whether this item is required for launch',
893
+ },
894
+ is_blocking: {
895
+ type: 'boolean',
896
+ description: 'Whether this item blocks the launch',
897
+ },
898
+ external_link: {
899
+ type: 'string',
900
+ description: 'External URL (null to clear)',
901
+ },
902
+ evidence_url: {
903
+ type: 'string',
904
+ description: 'URL to evidence of completion (null to clear)',
905
+ },
906
+ jira_ticket_key: {
907
+ type: 'string',
908
+ description: 'Associated Jira ticket key (null to clear)',
909
+ },
910
+ },
911
+ required: ['launch_id', 'item_id'],
912
+ },
913
+ },
914
+ {
915
+ name: 'fcp_delete_checklist_item',
916
+ description: 'Delete a checklist item from a launch. This action cannot be undone.',
917
+ inputSchema: {
918
+ type: 'object',
919
+ properties: {
920
+ launch_id: {
921
+ type: 'number',
922
+ description: 'The ID of the launch',
923
+ },
924
+ item_id: {
925
+ type: 'number',
926
+ description: 'The ID of the checklist item to delete',
643
927
  },
644
928
  },
645
- required: ['launch_id', 'item_id', 'status'],
929
+ required: ['launch_id', 'item_id'],
646
930
  },
647
931
  },
648
932
  {
@@ -677,109 +961,109 @@ const TOOLS = [
677
961
  required: ['launch_id'],
678
962
  },
679
963
  },
680
- // Unroo Task Management Tools
681
- {
682
- name: 'unroo_list_projects',
683
- description: 'List all Unroo projects mapped to FCP clients. Returns project mappings with organization and JIRA key info.',
684
- inputSchema: {
685
- type: 'object',
686
- properties: {},
687
- },
688
- },
964
+ // Launch CRUD Tools
689
965
  {
690
- name: 'unroo_list_tasks',
691
- description: 'List tasks from Unroo with optional filters. Use to find tasks for a project, by status, or by assignee.',
966
+ name: 'fcp_create_launch',
967
+ description: 'Create a new launch in FCP. Requires name, platform, launch_type, and target_launch_date. Optionally creates a default checklist.',
692
968
  inputSchema: {
693
969
  type: 'object',
694
970
  properties: {
695
- project_key: {
971
+ name: {
696
972
  type: 'string',
697
- description: 'Filter by JIRA project key or FCP-SITE-{id}',
973
+ description: 'Launch name (required)',
698
974
  },
699
- status: {
975
+ platform: {
700
976
  type: 'string',
701
- description: 'Filter by status: To Do, In Progress, Done, Blocked (comma-separated for multiple)',
977
+ enum: ['wordpress', 'drupal', 'nextjs', 'other'],
978
+ description: 'Platform type (required)',
702
979
  },
703
- assignee_email: {
980
+ launch_type: {
704
981
  type: 'string',
705
- description: 'Filter by assignee email',
982
+ enum: ['new_site', 'migration', 'redesign', 'replatform', 'hosting_only', 'maintenance'],
983
+ description: 'Type of launch (required)',
706
984
  },
707
- external_source_type: {
985
+ target_launch_date: {
708
986
  type: 'string',
709
- description: 'Filter by source: fcp, fcp_checklist, fcp_launch',
987
+ description: 'Target launch date in ISO format (required)',
710
988
  },
711
- limit: {
989
+ website_id: {
712
990
  type: 'number',
713
- description: 'Maximum number of tasks to return (default 100)',
991
+ description: 'Associated website ID',
714
992
  },
715
- },
716
- },
717
- },
718
- {
719
- name: 'unroo_create_task',
720
- description: 'Create a new task in Unroo. Requires title and project_key. Use for follow-up tasks, discovered issues, or new work items.',
721
- inputSchema: {
722
- type: 'object',
723
- properties: {
724
- title: {
725
- type: 'string',
726
- description: 'Task title (required)',
993
+ account_id: {
994
+ type: 'number',
995
+ description: 'Associated account/client ID',
727
996
  },
728
997
  description: {
729
998
  type: 'string',
730
- description: 'Detailed description of the task',
999
+ description: 'Launch description',
731
1000
  },
732
- project_key: {
1001
+ soft_launch_date: {
733
1002
  type: 'string',
734
- description: 'JIRA project key or FCP-SITE-{id} (required)',
1003
+ description: 'Soft launch date in ISO format',
735
1004
  },
736
- priority: {
1005
+ kickoff_date: {
737
1006
  type: 'string',
738
- enum: ['urgent', 'high', 'medium', 'low'],
739
- description: 'Task priority (default: medium)',
1007
+ description: 'Kickoff date in ISO format',
740
1008
  },
741
- status: {
1009
+ priority: {
742
1010
  type: 'string',
743
- description: 'Initial status (default: To Do)',
1011
+ enum: ['low', 'medium', 'high', 'critical'],
1012
+ description: 'Launch priority (default: medium)',
744
1013
  },
745
- assignee_email: {
1014
+ jira_project_key: {
746
1015
  type: 'string',
747
- description: 'Email of person to assign the task to',
1016
+ description: 'Jira project key',
748
1017
  },
749
- due_date: {
750
- type: 'string',
751
- description: 'Due date in ISO format',
1018
+ unroo_project_id: {
1019
+ type: 'number',
1020
+ description: 'Unroo project ID to link',
752
1021
  },
753
- hours_estimated: {
1022
+ webops_lead_id: {
754
1023
  type: 'number',
755
- description: 'Estimated hours to complete',
1024
+ description: 'WebOps lead user ID',
756
1025
  },
757
- labels: {
758
- type: 'array',
759
- items: { type: 'string' },
760
- description: 'Labels/tags for the task',
1026
+ devops_lead_id: {
1027
+ type: 'number',
1028
+ description: 'DevOps lead user ID',
761
1029
  },
762
- parent_issue_id: {
1030
+ dev_lead_id: {
1031
+ type: 'number',
1032
+ description: 'Dev lead user ID',
1033
+ },
1034
+ seo_lead_id: {
1035
+ type: 'number',
1036
+ description: 'SEO lead user ID',
1037
+ },
1038
+ client_contact: {
763
1039
  type: 'string',
764
- description: 'Parent task ID if this is a subtask',
1040
+ description: 'Client contact name',
1041
+ },
1042
+ client_contact_email: {
1043
+ type: 'string',
1044
+ description: 'Client contact email',
1045
+ },
1046
+ use_default_checklist: {
1047
+ type: 'boolean',
1048
+ description: 'Whether to create default checklist items (default: true)',
765
1049
  },
766
1050
  },
767
- required: ['title', 'project_key'],
1051
+ required: ['name', 'platform', 'launch_type', 'target_launch_date'],
768
1052
  },
769
1053
  },
770
1054
  {
771
- name: 'unroo_update_task',
772
- description: 'Update an existing task in Unroo. Use to change status, log hours, update priority, or reassign.',
1055
+ name: 'fcp_update_launch',
1056
+ description: 'Update an existing launch. Only provided fields are updated. Can change status, dates, assignments, and other properties.',
773
1057
  inputSchema: {
774
1058
  type: 'object',
775
1059
  properties: {
776
- task_id: {
777
- type: 'string',
778
- description: 'The ID of the task to update (required)',
1060
+ launch_id: {
1061
+ type: 'number',
1062
+ description: 'The ID of the launch to update (required)',
779
1063
  },
780
- title: {
1064
+ name: {
781
1065
  type: 'string',
782
- description: 'Updated title',
1066
+ description: 'Updated launch name',
783
1067
  },
784
1068
  description: {
785
1069
  type: 'string',
@@ -787,31 +1071,293 @@ const TOOLS = [
787
1071
  },
788
1072
  status: {
789
1073
  type: 'string',
790
- description: 'New status: To Do, In Progress, Done, Blocked',
1074
+ enum: ['planning', 'in_progress', 'soft_launched', 'launched', 'on_hold', 'cancelled'],
1075
+ description: 'Updated status',
1076
+ },
1077
+ platform: {
1078
+ type: 'string',
1079
+ enum: ['wordpress', 'drupal', 'nextjs', 'other'],
1080
+ description: 'Updated platform',
1081
+ },
1082
+ launch_type: {
1083
+ type: 'string',
1084
+ enum: ['new_site', 'migration', 'redesign', 'replatform', 'hosting_only', 'maintenance'],
1085
+ description: 'Updated launch type',
1086
+ },
1087
+ target_launch_date: {
1088
+ type: 'string',
1089
+ description: 'Updated target launch date',
1090
+ },
1091
+ soft_launch_date: {
1092
+ type: 'string',
1093
+ description: 'Updated soft launch date (null to clear)',
1094
+ },
1095
+ actual_launch_date: {
1096
+ type: 'string',
1097
+ description: 'Actual launch date when launched',
1098
+ },
1099
+ kickoff_date: {
1100
+ type: 'string',
1101
+ description: 'Updated kickoff date (null to clear)',
791
1102
  },
792
1103
  priority: {
793
1104
  type: 'string',
794
- enum: ['urgent', 'high', 'medium', 'low'],
1105
+ enum: ['low', 'medium', 'high', 'critical'],
795
1106
  description: 'Updated priority',
796
1107
  },
797
- assignee_email: {
1108
+ jira_project_key: {
798
1109
  type: 'string',
799
- description: 'New assignee email (null to unassign)',
1110
+ description: 'Jira project key (null to clear)',
800
1111
  },
801
- hours_logged: {
1112
+ unroo_project_id: {
802
1113
  type: 'number',
803
- description: 'Total hours logged on the task',
1114
+ description: 'Unroo project ID (null to unlink)',
804
1115
  },
805
- resolution: {
1116
+ webops_lead_id: {
1117
+ type: 'number',
1118
+ description: 'WebOps lead user ID (null to clear)',
1119
+ },
1120
+ devops_lead_id: {
1121
+ type: 'number',
1122
+ description: 'DevOps lead user ID (null to clear)',
1123
+ },
1124
+ dev_lead_id: {
1125
+ type: 'number',
1126
+ description: 'Dev lead user ID (null to clear)',
1127
+ },
1128
+ seo_lead_id: {
1129
+ type: 'number',
1130
+ description: 'SEO lead user ID (null to clear)',
1131
+ },
1132
+ client_contact: {
806
1133
  type: 'string',
807
- description: 'Resolution when marking as Done',
1134
+ description: 'Client contact name (null to clear)',
1135
+ },
1136
+ client_contact_email: {
1137
+ type: 'string',
1138
+ description: 'Client contact email (null to clear)',
808
1139
  },
809
1140
  },
810
- required: ['task_id'],
1141
+ required: ['launch_id'],
811
1142
  },
812
1143
  },
813
1144
  {
814
- name: 'unroo_get_my_tasks',
1145
+ name: 'fcp_delete_launch',
1146
+ description: 'Delete a launch and all associated checklist items. This action cannot be undone.',
1147
+ inputSchema: {
1148
+ type: 'object',
1149
+ properties: {
1150
+ launch_id: {
1151
+ type: 'number',
1152
+ description: 'The ID of the launch to delete',
1153
+ },
1154
+ },
1155
+ required: ['launch_id'],
1156
+ },
1157
+ },
1158
+ // Validation / Success Factor Tools
1159
+ {
1160
+ name: 'fcp_get_success_factors',
1161
+ description: 'Get the success factors (validation criteria) for a checklist item. Shows what automated checks are configured.',
1162
+ inputSchema: {
1163
+ type: 'object',
1164
+ properties: {
1165
+ launch_id: {
1166
+ type: 'number',
1167
+ description: 'The ID of the launch',
1168
+ },
1169
+ item_id: {
1170
+ type: 'number',
1171
+ description: 'The ID of the checklist item',
1172
+ },
1173
+ },
1174
+ required: ['launch_id', 'item_id'],
1175
+ },
1176
+ },
1177
+ {
1178
+ name: 'fcp_validate_checklist_item',
1179
+ description: 'Run automated validation checks on a checklist item. Executes all configured success factors (HTTP checks, DNS checks, SSL checks, etc.) and returns results.',
1180
+ inputSchema: {
1181
+ type: 'object',
1182
+ properties: {
1183
+ launch_id: {
1184
+ type: 'number',
1185
+ description: 'The ID of the launch',
1186
+ },
1187
+ item_id: {
1188
+ type: 'number',
1189
+ description: 'The ID of the checklist item to validate',
1190
+ },
1191
+ },
1192
+ required: ['launch_id', 'item_id'],
1193
+ },
1194
+ },
1195
+ {
1196
+ name: 'fcp_get_unroo_section',
1197
+ description: 'Generate the Unroo task management section for a CLAUDE.md file. Auto-detects project key from git remote. Use this to add Unroo integration instructions to any project.',
1198
+ inputSchema: {
1199
+ type: 'object',
1200
+ properties: {
1201
+ repo_url: {
1202
+ type: 'string',
1203
+ description: 'Git remote URL (e.g., git@github.com:owner/repo.git). If not provided, uses current directory.',
1204
+ },
1205
+ project_key: {
1206
+ type: 'string',
1207
+ description: 'Manual project key override. Use if auto-detection fails.',
1208
+ },
1209
+ },
1210
+ },
1211
+ },
1212
+ // Unroo Task Management Tools
1213
+ {
1214
+ name: 'unroo_list_projects',
1215
+ description: 'List all Unroo projects mapped to FCP clients. Returns project mappings with organization and JIRA key info.',
1216
+ inputSchema: {
1217
+ type: 'object',
1218
+ properties: {},
1219
+ },
1220
+ },
1221
+ {
1222
+ name: 'unroo_list_tasks',
1223
+ description: 'List tasks from Unroo with optional filters. Use to find tasks for a project, by status, or by assignee.',
1224
+ inputSchema: {
1225
+ type: 'object',
1226
+ properties: {
1227
+ project_key: {
1228
+ type: 'string',
1229
+ description: 'Filter by JIRA project key or FCP-SITE-{id}',
1230
+ },
1231
+ status: {
1232
+ type: 'string',
1233
+ description: 'Filter by status: To Do, In Progress, Done, Blocked (comma-separated for multiple)',
1234
+ },
1235
+ assignee_email: {
1236
+ type: 'string',
1237
+ description: 'Filter by assignee email',
1238
+ },
1239
+ external_source_type: {
1240
+ type: 'string',
1241
+ description: 'Filter by source: fcp, fcp_checklist, fcp_launch',
1242
+ },
1243
+ limit: {
1244
+ type: 'number',
1245
+ description: 'Maximum number of tasks to return (default 100)',
1246
+ },
1247
+ },
1248
+ },
1249
+ },
1250
+ {
1251
+ name: 'unroo_create_task',
1252
+ description: 'Create a new task in Unroo. Requires title and project_key. Use for follow-up tasks, discovered issues, or new work items.',
1253
+ inputSchema: {
1254
+ type: 'object',
1255
+ properties: {
1256
+ title: {
1257
+ type: 'string',
1258
+ description: 'Task title (required)',
1259
+ },
1260
+ description: {
1261
+ type: 'string',
1262
+ description: 'Detailed description of the task',
1263
+ },
1264
+ project_key: {
1265
+ type: 'string',
1266
+ description: 'JIRA project key or FCP-SITE-{id} (required)',
1267
+ },
1268
+ priority: {
1269
+ type: 'string',
1270
+ enum: ['urgent', 'high', 'medium', 'low'],
1271
+ description: 'Task priority (default: medium)',
1272
+ },
1273
+ status: {
1274
+ type: 'string',
1275
+ description: 'Initial status (default: To Do)',
1276
+ },
1277
+ assignee_email: {
1278
+ type: 'string',
1279
+ description: 'Email of person to assign the task to',
1280
+ },
1281
+ due_date: {
1282
+ type: 'string',
1283
+ description: 'Due date in ISO format',
1284
+ },
1285
+ hours_estimated: {
1286
+ type: 'number',
1287
+ description: 'Estimated hours to complete',
1288
+ },
1289
+ labels: {
1290
+ type: 'array',
1291
+ items: { type: 'string' },
1292
+ description: 'Labels/tags for the task',
1293
+ },
1294
+ parent_issue_id: {
1295
+ type: 'string',
1296
+ description: 'Parent task ID if this is a subtask',
1297
+ },
1298
+ },
1299
+ required: ['title', 'project_key'],
1300
+ },
1301
+ },
1302
+ {
1303
+ name: 'unroo_get_task',
1304
+ description: 'Get detailed information about a specific task by ID. Returns full task details including description, status, priority, labels, hours, and activity.',
1305
+ inputSchema: {
1306
+ type: 'object',
1307
+ properties: {
1308
+ task_id: {
1309
+ type: 'string',
1310
+ description: 'The ID of the task to retrieve (required)',
1311
+ },
1312
+ },
1313
+ required: ['task_id'],
1314
+ },
1315
+ },
1316
+ {
1317
+ name: 'unroo_update_task',
1318
+ description: 'Update an existing task in Unroo. Use to change status, log hours, update priority, or reassign.',
1319
+ inputSchema: {
1320
+ type: 'object',
1321
+ properties: {
1322
+ task_id: {
1323
+ type: 'string',
1324
+ description: 'The ID of the task to update (required)',
1325
+ },
1326
+ title: {
1327
+ type: 'string',
1328
+ description: 'Updated title',
1329
+ },
1330
+ description: {
1331
+ type: 'string',
1332
+ description: 'Updated description',
1333
+ },
1334
+ status: {
1335
+ type: 'string',
1336
+ description: 'New status: To Do, In Progress, Done, Blocked',
1337
+ },
1338
+ priority: {
1339
+ type: 'string',
1340
+ enum: ['urgent', 'high', 'medium', 'low'],
1341
+ description: 'Updated priority',
1342
+ },
1343
+ assignee_email: {
1344
+ type: 'string',
1345
+ description: 'New assignee email (null to unassign)',
1346
+ },
1347
+ hours_logged: {
1348
+ type: 'number',
1349
+ description: 'Total hours logged on the task',
1350
+ },
1351
+ resolution: {
1352
+ type: 'string',
1353
+ description: 'Resolution when marking as Done',
1354
+ },
1355
+ },
1356
+ required: ['task_id'],
1357
+ },
1358
+ },
1359
+ {
1360
+ name: 'unroo_get_my_tasks',
815
1361
  description: 'Get tasks assigned to the current user (based on API key). Useful for finding your work items.',
816
1362
  inputSchema: {
817
1363
  type: 'object',
@@ -994,17 +1540,399 @@ const TOOLS = [
994
1540
  type: 'string',
995
1541
  description: 'The ID of the parking lot item to convert (required)',
996
1542
  },
997
- priority: {
1543
+ priority: {
1544
+ type: 'string',
1545
+ enum: ['Urgent', 'High', 'Medium', 'Low'],
1546
+ description: 'Priority for the backlog item (optional, keeps original if not specified)',
1547
+ },
1548
+ notes: {
1549
+ type: 'string',
1550
+ description: 'Notes about the conversion decision',
1551
+ },
1552
+ },
1553
+ required: ['id'],
1554
+ },
1555
+ },
1556
+ // FileSync Tools
1557
+ {
1558
+ name: 'fcp_filesync_list_configs',
1559
+ description: 'List all file sync configurations with current status. Shows prod/staging PVC pairs, scheduling, sync direction, and last sync info.',
1560
+ inputSchema: {
1561
+ type: 'object',
1562
+ properties: {
1563
+ enabled: {
1564
+ type: 'boolean',
1565
+ description: 'Filter by enabled status',
1566
+ },
1567
+ search: {
1568
+ type: 'string',
1569
+ description: 'Search across site names, clusters, namespaces',
1570
+ },
1571
+ sync_direction: {
1572
+ type: 'string',
1573
+ enum: ['prod_to_staging', 'staging_to_prod'],
1574
+ description: 'Filter by sync direction',
1575
+ },
1576
+ },
1577
+ },
1578
+ },
1579
+ {
1580
+ name: 'fcp_filesync_get_config',
1581
+ description: 'Get detailed file sync config with recent job history. Returns full config, last 5 jobs, and scheduling info.',
1582
+ inputSchema: {
1583
+ type: 'object',
1584
+ properties: {
1585
+ config_id: {
1586
+ type: 'number',
1587
+ description: 'The config ID to retrieve',
1588
+ },
1589
+ },
1590
+ required: ['config_id'],
1591
+ },
1592
+ },
1593
+ {
1594
+ name: 'fcp_filesync_start_sync',
1595
+ description: 'Trigger a file sync immediately. For prod-to-staging syncs, executes directly. For staging-to-prod syncs, requires a confirmation_token from fcp_filesync_get_confirmation.',
1596
+ inputSchema: {
1597
+ type: 'object',
1598
+ properties: {
1599
+ config_id: {
1600
+ type: 'number',
1601
+ description: 'The config ID to sync',
1602
+ },
1603
+ confirmation_token: {
1604
+ type: 'string',
1605
+ description: 'Required for staging-to-prod syncs. Get from fcp_filesync_get_confirmation.',
1606
+ },
1607
+ },
1608
+ required: ['config_id'],
1609
+ },
1610
+ },
1611
+ {
1612
+ name: 'fcp_filesync_get_job_status',
1613
+ description: 'Get the status of a file sync job. Returns progress, current step, rsync stats, and timing info. Call in a loop to monitor a running sync.',
1614
+ inputSchema: {
1615
+ type: 'object',
1616
+ properties: {
1617
+ config_id: {
1618
+ type: 'number',
1619
+ description: 'The config ID to check jobs for',
1620
+ },
1621
+ status: {
1622
+ type: 'string',
1623
+ description: 'Filter by job status (e.g. pending, syncing, completed, failed)',
1624
+ },
1625
+ limit: {
1626
+ type: 'number',
1627
+ description: 'Number of jobs to return (default 5)',
1628
+ },
1629
+ },
1630
+ required: ['config_id'],
1631
+ },
1632
+ },
1633
+ {
1634
+ name: 'fcp_filesync_cancel_sync',
1635
+ description: 'Cancel a running file sync. Kills the K8s Job and marks the job as cancelled.',
1636
+ inputSchema: {
1637
+ type: 'object',
1638
+ properties: {
1639
+ job_id: {
1640
+ type: 'string',
1641
+ description: 'The job ID (UUID) to cancel',
1642
+ },
1643
+ },
1644
+ required: ['job_id'],
1645
+ },
1646
+ },
1647
+ {
1648
+ name: 'fcp_filesync_get_confirmation',
1649
+ description: 'Generate a confirmation token for staging-to-prod syncs. Token is valid for 5 minutes. Use before fcp_filesync_start_sync for staging_to_prod direction.',
1650
+ inputSchema: {
1651
+ type: 'object',
1652
+ properties: {
1653
+ config_id: {
1654
+ type: 'number',
1655
+ description: 'The config ID requiring confirmation',
1656
+ },
1657
+ },
1658
+ required: ['config_id'],
1659
+ },
1660
+ },
1661
+ // Nuclei Security Scanning
1662
+ {
1663
+ name: 'fcp_trigger_nuclei_scan',
1664
+ description: 'Trigger a Nuclei security scan for a website. Creates a Kubernetes Job that runs the scan and stores results in the FCP database.',
1665
+ inputSchema: {
1666
+ type: 'object',
1667
+ properties: {
1668
+ website_id: {
1669
+ type: 'number',
1670
+ description: 'The website ID to scan (required)',
1671
+ },
1672
+ url: {
1673
+ type: 'string',
1674
+ description: 'Override target URL (optional, auto-resolved from website record if not provided)',
1675
+ },
1676
+ severity: {
1677
+ type: 'string',
1678
+ description: 'Comma-separated severity levels to scan for (default: critical,high,medium)',
1679
+ },
1680
+ templates: {
1681
+ type: 'string',
1682
+ description: 'Comma-separated Nuclei template categories (default: cves,exposures,misconfiguration). Options: cves, vulnerabilities, exposures, misconfiguration, technologies',
1683
+ },
1684
+ },
1685
+ required: ['website_id'],
1686
+ },
1687
+ },
1688
+ {
1689
+ name: 'fcp_get_nuclei_results',
1690
+ description: 'Get Nuclei security scan results for a website. Returns scan history with findings summary and individual vulnerability details.',
1691
+ inputSchema: {
1692
+ type: 'object',
1693
+ properties: {
1694
+ website_id: {
1695
+ type: 'number',
1696
+ description: 'The website ID to get results for (required)',
1697
+ },
1698
+ scan_id: {
1699
+ type: 'string',
1700
+ description: 'Specific scan ID to get detailed results for (optional)',
1701
+ },
1702
+ limit: {
1703
+ type: 'number',
1704
+ description: 'Maximum number of scans to return (default: 5)',
1705
+ },
1706
+ status: {
1707
+ type: 'string',
1708
+ description: 'Filter findings by status: active, fixed, reopened, false_positive',
1709
+ },
1710
+ },
1711
+ required: ['website_id'],
1712
+ },
1713
+ },
1714
+ // Site/Website CRUD Tools
1715
+ {
1716
+ name: 'fcp_list_sites',
1717
+ description: 'List websites managed by FCP with optional filters. Returns paginated results with account info.',
1718
+ inputSchema: {
1719
+ type: 'object',
1720
+ properties: {
1721
+ account_id: {
1722
+ type: 'number',
1723
+ description: 'Filter by account/client ID',
1724
+ },
1725
+ cms: {
1726
+ type: 'string',
1727
+ description: 'Filter by CMS type: WordPress, Drupal, Strapi, Other',
1728
+ },
1729
+ environment: {
1730
+ type: 'string',
1731
+ description: 'Filter by environment: production, staging, development',
1732
+ },
1733
+ retired: {
1734
+ type: 'string',
1735
+ description: 'Filter retired sites: "true" (only retired), "all" (include retired), omit for active only',
1736
+ },
1737
+ limit: {
1738
+ type: 'number',
1739
+ description: 'Maximum results to return (default: 50, max: 200)',
1740
+ },
1741
+ offset: {
1742
+ type: 'number',
1743
+ description: 'Offset for pagination (default: 0)',
1744
+ },
1745
+ },
1746
+ },
1747
+ },
1748
+ {
1749
+ name: 'fcp_search_sites',
1750
+ description: 'Search websites by domain name, account name, or URL. Returns matching sites ranked by relevance.',
1751
+ inputSchema: {
1752
+ type: 'object',
1753
+ properties: {
1754
+ query: {
1755
+ type: 'string',
1756
+ description: 'Search query (min 2 characters) - matches against domain, account name, and URL',
1757
+ },
1758
+ limit: {
1759
+ type: 'number',
1760
+ description: 'Maximum results to return (default: 10)',
1761
+ },
1762
+ },
1763
+ required: ['query'],
1764
+ },
1765
+ },
1766
+ {
1767
+ name: 'fcp_get_site',
1768
+ description: 'Get detailed information about a specific website including account info, infrastructure details, and lead developer.',
1769
+ inputSchema: {
1770
+ type: 'object',
1771
+ properties: {
1772
+ website_id: {
1773
+ type: 'number',
1774
+ description: 'The website ID to retrieve',
1775
+ },
1776
+ },
1777
+ required: ['website_id'],
1778
+ },
1779
+ },
1780
+ {
1781
+ name: 'fcp_create_site',
1782
+ description: 'Create a new website with optional staging environments. Production site is created first, then staging sites are linked to it.',
1783
+ inputSchema: {
1784
+ type: 'object',
1785
+ properties: {
1786
+ account_id: {
1787
+ type: 'number',
1788
+ description: 'Account/client ID the site belongs to (required)',
1789
+ },
1790
+ domain: {
1791
+ type: 'string',
1792
+ description: 'Primary domain name, e.g. "example.com" (required)',
1793
+ },
1794
+ cms: {
1795
+ type: 'string',
1796
+ description: 'CMS type: WordPress, Drupal, Strapi, Other (required)',
1797
+ },
1798
+ url_full: {
1799
+ type: 'string',
1800
+ description: 'Full URL including protocol (default: https://<domain>)',
1801
+ },
1802
+ git_provider: {
1803
+ type: 'string',
1804
+ description: 'Git provider: GitHub, GitLab, Bitbucket (default: GitHub)',
1805
+ },
1806
+ git_link: {
1807
+ type: 'string',
1808
+ description: 'URL to the git repository',
1809
+ },
1810
+ hosting_provider: {
1811
+ type: 'string',
1812
+ description: 'Hosting provider name',
1813
+ },
1814
+ fru_hosted: {
1815
+ type: 'boolean',
1816
+ description: 'Whether the site is hosted on FruCloud (default: false)',
1817
+ },
1818
+ k8s_cluster: {
1819
+ type: 'string',
1820
+ description: 'Kubernetes cluster name if FruCloud hosted',
1821
+ },
1822
+ k8s_namespace: {
1823
+ type: 'string',
1824
+ description: 'Kubernetes namespace if FruCloud hosted',
1825
+ },
1826
+ staging: {
1827
+ type: 'array',
1828
+ items: {
1829
+ type: 'object',
1830
+ properties: {
1831
+ domain: { type: 'string', description: 'Staging domain' },
1832
+ k8s_namespace: { type: 'string', description: 'Staging K8s namespace' },
1833
+ },
1834
+ required: ['domain', 'k8s_namespace'],
1835
+ },
1836
+ description: 'Optional staging environments to create',
1837
+ },
1838
+ },
1839
+ required: ['account_id', 'domain', 'cms'],
1840
+ },
1841
+ },
1842
+ {
1843
+ name: 'fcp_update_site',
1844
+ description: 'Update properties of an existing website. Only provided fields are updated.',
1845
+ inputSchema: {
1846
+ type: 'object',
1847
+ properties: {
1848
+ website_id: {
1849
+ type: 'number',
1850
+ description: 'The website ID to update (required)',
1851
+ },
1852
+ domain: {
1853
+ type: 'string',
1854
+ description: 'Updated domain name',
1855
+ },
1856
+ url_full: {
1857
+ type: 'string',
1858
+ description: 'Updated full URL',
1859
+ },
1860
+ cms: {
1861
+ type: 'string',
1862
+ description: 'Updated CMS type',
1863
+ },
1864
+ git_provider: {
1865
+ type: 'string',
1866
+ description: 'Updated git provider',
1867
+ },
1868
+ git_link: {
1869
+ type: 'string',
1870
+ description: 'Updated git repository URL',
1871
+ },
1872
+ staging_url: {
1873
+ type: 'string',
1874
+ description: 'Updated staging URL',
1875
+ },
1876
+ hosting_provider: {
1877
+ type: 'string',
1878
+ description: 'Updated hosting provider',
1879
+ },
1880
+ fru_hosted: {
1881
+ type: 'boolean',
1882
+ description: 'Updated FruCloud hosting flag',
1883
+ },
1884
+ k8s_cluster: {
1885
+ type: 'string',
1886
+ description: 'Updated K8s cluster',
1887
+ },
1888
+ k8s_namespace: {
1889
+ type: 'string',
1890
+ description: 'Updated K8s namespace',
1891
+ },
1892
+ environment: {
1893
+ type: 'string',
1894
+ description: 'Updated environment: production, staging, development',
1895
+ },
1896
+ lead_developer: {
1897
+ type: 'number',
1898
+ description: 'Updated lead developer user ID',
1899
+ },
1900
+ frucare_site: {
1901
+ type: 'boolean',
1902
+ description: 'Updated FruCare maintenance flag',
1903
+ },
1904
+ backup_enabled: {
1905
+ type: 'boolean',
1906
+ description: 'Updated backup enabled flag',
1907
+ },
1908
+ backup_schedule: {
1909
+ type: 'string',
1910
+ description: 'Updated backup cron schedule',
1911
+ },
1912
+ },
1913
+ required: ['website_id'],
1914
+ },
1915
+ },
1916
+ {
1917
+ name: 'fcp_delete_site',
1918
+ description: 'Soft-delete (retire) a website. Sets retired flag and preserves all data. Admin only.',
1919
+ inputSchema: {
1920
+ type: 'object',
1921
+ properties: {
1922
+ website_id: {
1923
+ type: 'number',
1924
+ description: 'The website ID to retire (required)',
1925
+ },
1926
+ reason: {
998
1927
  type: 'string',
999
- enum: ['Urgent', 'High', 'Medium', 'Low'],
1000
- description: 'Priority for the backlog item (optional, keeps original if not specified)',
1928
+ description: 'Reason for retiring the site',
1001
1929
  },
1002
- notes: {
1930
+ forward_url: {
1003
1931
  type: 'string',
1004
- description: 'Notes about the conversion decision',
1932
+ description: 'URL to forward the domain to after retirement',
1005
1933
  },
1006
1934
  },
1007
- required: ['id'],
1935
+ required: ['website_id'],
1008
1936
  },
1009
1937
  },
1010
1938
  ];
@@ -1096,11 +2024,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1096
2024
  ],
1097
2025
  };
1098
2026
  }
1099
- case 'fcp_update_checklist_item': {
1100
- const { launch_id, item_id, status, notes } = args;
1101
- const result = await client.updateChecklistItem(launch_id, item_id, {
1102
- status,
1103
- notes,
2027
+ case 'fcp_add_checklist_item': {
2028
+ const { launch_id, title, category, ...rest } = args;
2029
+ const result = await client.createChecklistItem(launch_id, {
2030
+ title,
2031
+ category,
2032
+ ...rest,
1104
2033
  });
1105
2034
  return {
1106
2035
  content: [
@@ -1108,13 +2037,49 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1108
2037
  type: 'text',
1109
2038
  text: JSON.stringify({
1110
2039
  success: true,
1111
- message: `Checklist item ${item_id} updated to status: ${status}`,
2040
+ message: `Checklist item created: "${title}"`,
2041
+ item: result,
2042
+ }, null, 2),
2043
+ },
2044
+ ],
2045
+ };
2046
+ }
2047
+ case 'fcp_update_checklist_item': {
2048
+ const { launch_id, item_id, notes, ...updates } = args;
2049
+ // Map 'notes' to 'completion_notes' for the API
2050
+ const payload = { ...updates };
2051
+ if (notes !== undefined) {
2052
+ payload.completion_notes = notes;
2053
+ }
2054
+ const result = await client.updateChecklistItem(launch_id, item_id, payload);
2055
+ return {
2056
+ content: [
2057
+ {
2058
+ type: 'text',
2059
+ text: JSON.stringify({
2060
+ success: true,
2061
+ message: `Checklist item ${item_id} updated`,
1112
2062
  item: result,
1113
2063
  }, null, 2),
1114
2064
  },
1115
2065
  ],
1116
2066
  };
1117
2067
  }
2068
+ case 'fcp_delete_checklist_item': {
2069
+ const { launch_id, item_id } = args;
2070
+ await client.deleteChecklistItem(launch_id, item_id);
2071
+ return {
2072
+ content: [
2073
+ {
2074
+ type: 'text',
2075
+ text: JSON.stringify({
2076
+ success: true,
2077
+ message: `Checklist item ${item_id} deleted from launch ${launch_id}`,
2078
+ }, null, 2),
2079
+ },
2080
+ ],
2081
+ };
2082
+ }
1118
2083
  case 'fcp_add_progress_note': {
1119
2084
  const { launch_id, content } = args;
1120
2085
  const result = await client.addNote(launch_id, content);
@@ -1143,6 +2108,244 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1143
2108
  ],
1144
2109
  };
1145
2110
  }
2111
+ // Nuclei Security Scanning Handlers
2112
+ case 'fcp_trigger_nuclei_scan': {
2113
+ const { website_id, url, severity, templates } = args;
2114
+ const result = await client.triggerNucleiScan(website_id, { url, severity, templates });
2115
+ return {
2116
+ content: [
2117
+ {
2118
+ type: 'text',
2119
+ text: JSON.stringify(result, null, 2),
2120
+ },
2121
+ ],
2122
+ };
2123
+ }
2124
+ case 'fcp_get_nuclei_results': {
2125
+ const { website_id, scan_id, limit, status } = args;
2126
+ const result = await client.getNucleiResults(website_id, { scan_id, limit, status });
2127
+ return {
2128
+ content: [
2129
+ {
2130
+ type: 'text',
2131
+ text: JSON.stringify(result, null, 2),
2132
+ },
2133
+ ],
2134
+ };
2135
+ }
2136
+ // Site/Website CRUD Handlers
2137
+ case 'fcp_list_sites': {
2138
+ const filters = args;
2139
+ const result = await client.listSites(filters);
2140
+ return {
2141
+ content: [
2142
+ {
2143
+ type: 'text',
2144
+ text: JSON.stringify(result, null, 2),
2145
+ },
2146
+ ],
2147
+ };
2148
+ }
2149
+ case 'fcp_search_sites': {
2150
+ const { query, limit } = args;
2151
+ const result = await client.searchSites(query, limit);
2152
+ return {
2153
+ content: [
2154
+ {
2155
+ type: 'text',
2156
+ text: JSON.stringify(result, null, 2),
2157
+ },
2158
+ ],
2159
+ };
2160
+ }
2161
+ case 'fcp_get_site': {
2162
+ const { website_id } = args;
2163
+ const result = await client.getSite(website_id);
2164
+ return {
2165
+ content: [
2166
+ {
2167
+ type: 'text',
2168
+ text: JSON.stringify(result, null, 2),
2169
+ },
2170
+ ],
2171
+ };
2172
+ }
2173
+ case 'fcp_create_site': {
2174
+ const { account_id, domain, cms, url_full, git_provider, git_link, hosting_provider, fru_hosted, k8s_cluster, k8s_namespace, staging, } = args;
2175
+ const result = await client.createSite({ account_id, domain, cms, url_full, git_provider, git_link, hosting_provider, fru_hosted, k8s_cluster, k8s_namespace }, staging);
2176
+ return {
2177
+ content: [
2178
+ {
2179
+ type: 'text',
2180
+ text: JSON.stringify(result, null, 2),
2181
+ },
2182
+ ],
2183
+ };
2184
+ }
2185
+ case 'fcp_update_site': {
2186
+ const { website_id, ...updates } = args;
2187
+ const result = await client.updateSite(website_id, updates);
2188
+ return {
2189
+ content: [
2190
+ {
2191
+ type: 'text',
2192
+ text: JSON.stringify(result, null, 2),
2193
+ },
2194
+ ],
2195
+ };
2196
+ }
2197
+ case 'fcp_delete_site': {
2198
+ const { website_id, reason, forward_url } = args;
2199
+ const result = await client.deleteSite(website_id, { reason, forward_url });
2200
+ return {
2201
+ content: [
2202
+ {
2203
+ type: 'text',
2204
+ text: JSON.stringify(result, null, 2),
2205
+ },
2206
+ ],
2207
+ };
2208
+ }
2209
+ // Launch CRUD Handlers
2210
+ case 'fcp_create_launch': {
2211
+ const { name, platform, launch_type, target_launch_date, ...rest } = args;
2212
+ const result = await client.createLaunch({
2213
+ name,
2214
+ platform,
2215
+ launch_type,
2216
+ target_launch_date,
2217
+ ...rest,
2218
+ });
2219
+ return {
2220
+ content: [
2221
+ {
2222
+ type: 'text',
2223
+ text: JSON.stringify({
2224
+ success: true,
2225
+ message: `Launch created: "${name}" (ID: ${result.launch.id})`,
2226
+ launch: result.launch,
2227
+ }, null, 2),
2228
+ },
2229
+ ],
2230
+ };
2231
+ }
2232
+ case 'fcp_update_launch': {
2233
+ const { launch_id, ...updates } = args;
2234
+ const result = await client.updateLaunch(launch_id, updates);
2235
+ return {
2236
+ content: [
2237
+ {
2238
+ type: 'text',
2239
+ text: JSON.stringify({
2240
+ success: true,
2241
+ message: `Launch ${launch_id} updated`,
2242
+ launch: result.launch || result,
2243
+ }, null, 2),
2244
+ },
2245
+ ],
2246
+ };
2247
+ }
2248
+ case 'fcp_delete_launch': {
2249
+ const { launch_id } = args;
2250
+ await client.deleteLaunch(launch_id);
2251
+ return {
2252
+ content: [
2253
+ {
2254
+ type: 'text',
2255
+ text: JSON.stringify({
2256
+ success: true,
2257
+ message: `Launch ${launch_id} deleted`,
2258
+ }, null, 2),
2259
+ },
2260
+ ],
2261
+ };
2262
+ }
2263
+ // Validation / Success Factor Handlers
2264
+ case 'fcp_get_success_factors': {
2265
+ const { launch_id, item_id } = args;
2266
+ const result = await client.getSuccessFactors(launch_id, item_id);
2267
+ return {
2268
+ content: [
2269
+ {
2270
+ type: 'text',
2271
+ text: JSON.stringify({
2272
+ checklist_item_id: item_id,
2273
+ total: result.factors?.length || 0,
2274
+ factors: result.factors || [],
2275
+ }, null, 2),
2276
+ },
2277
+ ],
2278
+ };
2279
+ }
2280
+ case 'fcp_validate_checklist_item': {
2281
+ const { launch_id, item_id } = args;
2282
+ const result = await client.validateChecklistItem(launch_id, item_id);
2283
+ return {
2284
+ content: [
2285
+ {
2286
+ type: 'text',
2287
+ text: JSON.stringify(result, null, 2),
2288
+ },
2289
+ ],
2290
+ };
2291
+ }
2292
+ case 'fcp_get_unroo_section': {
2293
+ const { repo_url, project_key } = args;
2294
+ // Use provided repo_url or detect from current directory
2295
+ const repoUrlToUse = repo_url || detectGitRemote();
2296
+ if (!repoUrlToUse && !project_key) {
2297
+ return {
2298
+ content: [
2299
+ {
2300
+ type: 'text',
2301
+ text: JSON.stringify({
2302
+ error: 'Could not detect git remote and no project_key provided. Either run this from a git repository or provide a project_key.',
2303
+ }, null, 2),
2304
+ },
2305
+ ],
2306
+ isError: true,
2307
+ };
2308
+ }
2309
+ // Build query params
2310
+ const sectionParams = new URLSearchParams();
2311
+ if (repoUrlToUse)
2312
+ sectionParams.set('repo_url', repoUrlToUse);
2313
+ if (project_key)
2314
+ sectionParams.set('project_key', project_key);
2315
+ // Call FCP API to get the section
2316
+ const sectionUrl = `${FCP_API_URL}/api/mcp/claude-md-section?${sectionParams}`;
2317
+ const sectionHeaders = {
2318
+ 'Content-Type': 'application/json',
2319
+ };
2320
+ if (FCP_API_TOKEN && FCP_API_TOKEN !== 'dev_bypass') {
2321
+ sectionHeaders['X-API-Key'] = FCP_API_TOKEN;
2322
+ }
2323
+ else if (FCP_API_TOKEN === 'dev_bypass') {
2324
+ sectionHeaders['X-Dev-Bypass'] = 'true';
2325
+ }
2326
+ const response = await fetch(sectionUrl, { headers: sectionHeaders });
2327
+ const data = await response.json();
2328
+ if (!response.ok) {
2329
+ return {
2330
+ content: [
2331
+ {
2332
+ type: 'text',
2333
+ text: JSON.stringify({ error: data.error || 'Failed to get section' }, null, 2),
2334
+ },
2335
+ ],
2336
+ isError: true,
2337
+ };
2338
+ }
2339
+ // Return the markdown content with metadata
2340
+ return {
2341
+ content: [
2342
+ {
2343
+ type: 'text',
2344
+ text: `Project Key: ${data.project_key}\nSource: ${data.source}\n${data.warning ? `Warning: ${data.warning}\n` : ''}\n---\n\n${data.content}`,
2345
+ },
2346
+ ],
2347
+ };
2348
+ }
1146
2349
  // Unroo Task Management Handlers
1147
2350
  case 'unroo_list_projects': {
1148
2351
  const result = await unrooClient.listProjects();
@@ -1190,6 +2393,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1190
2393
  ],
1191
2394
  };
1192
2395
  }
2396
+ case 'unroo_get_task': {
2397
+ const { task_id } = args;
2398
+ const result = await unrooClient.getTask(task_id);
2399
+ return {
2400
+ content: [
2401
+ {
2402
+ type: 'text',
2403
+ text: JSON.stringify({
2404
+ success: true,
2405
+ task: result.task,
2406
+ }, null, 2),
2407
+ },
2408
+ ],
2409
+ };
2410
+ }
1193
2411
  case 'unroo_update_task': {
1194
2412
  const { task_id, ...updates } = args;
1195
2413
  const result = await unrooClient.updateTask(task_id, updates);
@@ -1250,9 +2468,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1250
2468
  };
1251
2469
  }
1252
2470
  case 'unroo_end_session': {
1253
- // End both explicit and auto-tracked sessions
2471
+ // End the auto-tracked session (clears heartbeat interval, logs activity)
1254
2472
  await sessionTracker.endSession();
1255
- const result = await unrooClient.endSession();
2473
+ // End session via API to get the final session state
2474
+ // Note: If sessionTracker already ended it, Unroo returns the existing completed session
2475
+ // (protected against duplicate work log creation server-side)
2476
+ const result = await unrooClient.endSession({
2477
+ machine_id: INSTANCE_ID,
2478
+ repo_name: currentProject ? `${currentProject.github.owner}/${currentProject.github.repo}` : undefined,
2479
+ });
1256
2480
  return {
1257
2481
  content: [
1258
2482
  {
@@ -1380,6 +2604,178 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1380
2604
  ],
1381
2605
  };
1382
2606
  }
2607
+ // FileSync Handlers
2608
+ case 'fcp_filesync_list_configs': {
2609
+ const filters = args;
2610
+ const result = await client.listFileSyncConfigs(filters);
2611
+ // If search is provided, filter client-side (API doesn't have text search)
2612
+ let configs = result.data || [];
2613
+ if (filters.search) {
2614
+ const term = filters.search.toLowerCase();
2615
+ configs = configs.filter((c) => (c.prod_namespace || '').toLowerCase().includes(term) ||
2616
+ (c.staging_namespace || '').toLowerCase().includes(term) ||
2617
+ (c.prod_cluster || '').toLowerCase().includes(term) ||
2618
+ (c.staging_cluster || '').toLowerCase().includes(term) ||
2619
+ (c.prod_pvc_name || '').toLowerCase().includes(term) ||
2620
+ (c.staging_pvc_name || '').toLowerCase().includes(term));
2621
+ }
2622
+ return {
2623
+ content: [
2624
+ {
2625
+ type: 'text',
2626
+ text: JSON.stringify({
2627
+ total: configs.length,
2628
+ configs: configs.map((c) => ({
2629
+ id: c.id,
2630
+ prod_namespace: c.prod_namespace,
2631
+ staging_namespace: c.staging_namespace,
2632
+ prod_cluster: c.prod_cluster,
2633
+ staging_cluster: c.staging_cluster,
2634
+ sync_direction: c.sync_direction,
2635
+ sync_method: c.sync_method,
2636
+ enabled: c.enabled,
2637
+ schedule_enabled: c.schedule_enabled,
2638
+ schedule_cron: c.schedule_cron,
2639
+ last_sync_at: c.last_sync_at,
2640
+ last_sync_status: c.last_sync_status,
2641
+ })),
2642
+ }, null, 2),
2643
+ },
2644
+ ],
2645
+ };
2646
+ }
2647
+ case 'fcp_filesync_get_config': {
2648
+ const { config_id } = args;
2649
+ // Get config from list (filtered by looking through all)
2650
+ const configsResult = await client.listFileSyncConfigs();
2651
+ const config = (configsResult.data || []).find((c) => c.id === config_id);
2652
+ if (!config) {
2653
+ throw new Error(`Config not found: ${config_id}`);
2654
+ }
2655
+ // Get recent jobs for this config
2656
+ const jobsResult = await client.getFileSyncJobs({ config_id, limit: 5 });
2657
+ return {
2658
+ content: [
2659
+ {
2660
+ type: 'text',
2661
+ text: JSON.stringify({
2662
+ config,
2663
+ recent_jobs: (jobsResult.data || []).map((j) => ({
2664
+ id: j.id,
2665
+ job_id: j.job_id,
2666
+ status: j.status,
2667
+ trigger_type: j.trigger_type,
2668
+ progress_percent: j.progress_percent,
2669
+ current_step: j.current_step,
2670
+ started_at: j.started_at,
2671
+ completed_at: j.completed_at,
2672
+ duration_seconds: j.duration_seconds,
2673
+ files_transferred: j.files_transferred,
2674
+ bytes_transferred: j.bytes_transferred,
2675
+ error_message: j.error_message,
2676
+ })),
2677
+ }, null, 2),
2678
+ },
2679
+ ],
2680
+ };
2681
+ }
2682
+ case 'fcp_filesync_start_sync': {
2683
+ const { config_id, confirmation_token } = args;
2684
+ console.error(`[MCP] Starting file sync for config ${config_id}`);
2685
+ const result = await client.startFileSync({
2686
+ config_id,
2687
+ trigger_type: 'api',
2688
+ triggered_by: 'claude-code-mcp',
2689
+ confirmation_token,
2690
+ });
2691
+ return {
2692
+ content: [
2693
+ {
2694
+ type: 'text',
2695
+ text: JSON.stringify({
2696
+ success: result.success,
2697
+ message: result.message,
2698
+ job: result.data ? {
2699
+ id: result.data.id,
2700
+ job_id: result.data.job_id,
2701
+ status: result.data.status,
2702
+ current_step: result.data.current_step,
2703
+ } : null,
2704
+ }, null, 2),
2705
+ },
2706
+ ],
2707
+ };
2708
+ }
2709
+ case 'fcp_filesync_get_job_status': {
2710
+ const { config_id, status, limit } = args;
2711
+ const result = await client.getFileSyncJobs({
2712
+ config_id,
2713
+ status,
2714
+ limit: limit || 5,
2715
+ });
2716
+ return {
2717
+ content: [
2718
+ {
2719
+ type: 'text',
2720
+ text: JSON.stringify({
2721
+ total: (result.data || []).length,
2722
+ jobs: (result.data || []).map((j) => ({
2723
+ id: j.id,
2724
+ job_id: j.job_id,
2725
+ status: j.status,
2726
+ trigger_type: j.trigger_type,
2727
+ progress_percent: j.progress_percent,
2728
+ current_step: j.current_step,
2729
+ started_at: j.started_at,
2730
+ completed_at: j.completed_at,
2731
+ duration_seconds: j.duration_seconds,
2732
+ files_transferred: j.files_transferred,
2733
+ files_deleted: j.files_deleted,
2734
+ bytes_transferred: j.bytes_transferred,
2735
+ error_message: j.error_message,
2736
+ rsync_stats: j.rsync_stats,
2737
+ })),
2738
+ }, null, 2),
2739
+ },
2740
+ ],
2741
+ };
2742
+ }
2743
+ case 'fcp_filesync_cancel_sync': {
2744
+ const { job_id } = args;
2745
+ console.error(`[MCP] Cancelling file sync job ${job_id}`);
2746
+ const result = await client.cancelFileSync(job_id);
2747
+ return {
2748
+ content: [
2749
+ {
2750
+ type: 'text',
2751
+ text: JSON.stringify({
2752
+ success: result.success,
2753
+ message: result.message,
2754
+ }, null, 2),
2755
+ },
2756
+ ],
2757
+ };
2758
+ }
2759
+ case 'fcp_filesync_get_confirmation': {
2760
+ const { config_id } = args;
2761
+ console.error(`[MCP] Getting confirmation token for config ${config_id}`);
2762
+ const result = await client.getFileSyncConfirmation(config_id);
2763
+ return {
2764
+ content: [
2765
+ {
2766
+ type: 'text',
2767
+ text: JSON.stringify({
2768
+ success: result.success,
2769
+ confirmation_token: result.data.token,
2770
+ config_id: result.data.config_id,
2771
+ expires_at: result.data.expires_at,
2772
+ ttl_seconds: result.data.ttl_seconds,
2773
+ warnings: result.data.warnings,
2774
+ }, null, 2),
2775
+ },
2776
+ ],
2777
+ };
2778
+ }
1383
2779
  default:
1384
2780
  throw new Error(`Unknown tool: ${name}`);
1385
2781
  }