@fruition/fcp-mcp-server 1.15.0 → 1.16.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 +210 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -41,6 +41,9 @@ const FCP_API_TOKEN = process.env.FCP_API_TOKEN || '';
41
41
  // When UNROO_API_KEY is not set, Unroo calls go through FCP's proxy at /api/mcp/unroo/*
42
42
  const UNROO_API_URL = process.env.UNROO_API_URL || 'https://app.unroo.io';
43
43
  const UNROO_API_KEY = process.env.UNROO_API_KEY || '';
44
+ // User identity for Unroo attribution
45
+ // Without this, all actions are attributed to whoever created the FCP API key
46
+ const FCP_USER_EMAIL = process.env.FCP_USER_EMAIL || '';
44
47
  // If no Unroo key, use FCP as proxy (unified key setup)
45
48
  const USE_FCP_UNROO_PROXY = !UNROO_API_KEY;
46
49
  // Helper to check if Unroo functionality is available (either mode)
@@ -466,6 +469,35 @@ class FCPClient {
466
469
  body: JSON.stringify({ s3Key }),
467
470
  });
468
471
  }
472
+ // Clone to Staging
473
+ async cloneToStagingConfirm(productionSiteId, stagingSiteId) {
474
+ return this.fetch('/api/clone-staging/confirm', {
475
+ method: 'POST',
476
+ body: JSON.stringify({ productionSiteId, stagingSiteId }),
477
+ });
478
+ }
479
+ async cloneToStaging(params) {
480
+ return this.fetch('/api/clone-staging', {
481
+ method: 'POST',
482
+ body: JSON.stringify(params),
483
+ });
484
+ }
485
+ async getCloneStatus(cloneId) {
486
+ return this.fetch(`/api/clone-staging/${encodeURIComponent(cloneId)}`);
487
+ }
488
+ async listCloneOperations(params) {
489
+ const query = new URLSearchParams();
490
+ if (params?.productionSiteId)
491
+ query.set('productionSiteId', String(params.productionSiteId));
492
+ if (params?.stagingSiteId)
493
+ query.set('stagingSiteId', String(params.stagingSiteId));
494
+ if (params?.limit)
495
+ query.set('limit', String(params.limit));
496
+ return this.fetch(`/api/clone-staging?${query.toString()}`);
497
+ }
498
+ async getDevEnvironmentInfo(siteId) {
499
+ return this.fetch(`/api/sites/${siteId}/dev-info`);
500
+ }
469
501
  }
470
502
  // Unroo API Client
471
503
  // Supports two modes:
@@ -508,6 +540,11 @@ class UnrooClient {
508
540
  else if (this.fcpToken === 'dev_bypass') {
509
541
  headers['X-Dev-Bypass'] = 'true';
510
542
  }
543
+ // Pass actual user identity so Unroo attributes actions correctly
544
+ // (otherwise all actions show as the FCP API key creator)
545
+ if (FCP_USER_EMAIL) {
546
+ headers['X-Acting-User-Email'] = FCP_USER_EMAIL;
547
+ }
511
548
  }
512
549
  else {
513
550
  // Direct Unroo API call
@@ -1460,6 +1497,14 @@ const TOOLS = [
1460
1497
  type: 'string',
1461
1498
  description: 'Parent task ID if this is a subtask',
1462
1499
  },
1500
+ github_branch: {
1501
+ type: 'string',
1502
+ description: 'Git branch name associated with this task',
1503
+ },
1504
+ github_repo: {
1505
+ type: 'string',
1506
+ description: 'GitHub repository (owner/repo format)',
1507
+ },
1463
1508
  },
1464
1509
  required: ['title', 'project_key'],
1465
1510
  },
@@ -1517,6 +1562,22 @@ const TOOLS = [
1517
1562
  type: 'string',
1518
1563
  description: 'Resolution when marking as Done',
1519
1564
  },
1565
+ github_branch: {
1566
+ type: 'string',
1567
+ description: 'Git branch name associated with this task',
1568
+ },
1569
+ github_pr_url: {
1570
+ type: 'string',
1571
+ description: 'GitHub pull request URL',
1572
+ },
1573
+ github_pr_number: {
1574
+ type: 'number',
1575
+ description: 'GitHub pull request number',
1576
+ },
1577
+ github_repo: {
1578
+ type: 'string',
1579
+ description: 'GitHub repository (owner/repo format)',
1580
+ },
1520
1581
  },
1521
1582
  required: ['task_id'],
1522
1583
  },
@@ -2584,6 +2645,108 @@ const TOOLS = [
2584
2645
  required: ['s3Key'],
2585
2646
  },
2586
2647
  },
2648
+ // Clone to Staging (unified prod → staging with files + database + search/replace)
2649
+ {
2650
+ name: 'fcp_clone_to_staging',
2651
+ description: 'Clone production to staging environment. Combines file sync (from DO snapshots) and database sync (from S3 backups pulled from RO replicas) into a single tracked operation. Requires a confirmation token from fcp_clone_confirm first, unless triggerType is "mcp".',
2652
+ inputSchema: {
2653
+ type: 'object',
2654
+ properties: {
2655
+ productionSiteId: {
2656
+ type: 'number',
2657
+ description: 'Production site ID',
2658
+ },
2659
+ stagingSiteId: {
2660
+ type: 'number',
2661
+ description: 'Staging site ID to clone into',
2662
+ },
2663
+ includeFiles: {
2664
+ type: 'boolean',
2665
+ description: 'Whether to sync files from production snapshot (default: true)',
2666
+ },
2667
+ includeDatabase: {
2668
+ type: 'boolean',
2669
+ description: 'Whether to sync database from latest backup (default: true)',
2670
+ },
2671
+ runSearchReplace: {
2672
+ type: 'boolean',
2673
+ description: 'Whether to run domain search/replace after DB sync (default: true)',
2674
+ },
2675
+ backupId: {
2676
+ type: 'string',
2677
+ description: 'Specific backup ID to restore from (optional, defaults to latest)',
2678
+ },
2679
+ },
2680
+ required: ['productionSiteId', 'stagingSiteId'],
2681
+ },
2682
+ },
2683
+ {
2684
+ name: 'fcp_clone_confirm',
2685
+ description: 'Generate a safety confirmation token for a clone-to-staging operation. Returns token (5-min TTL) plus site info and latest backup age.',
2686
+ inputSchema: {
2687
+ type: 'object',
2688
+ properties: {
2689
+ productionSiteId: {
2690
+ type: 'number',
2691
+ description: 'Production site ID',
2692
+ },
2693
+ stagingSiteId: {
2694
+ type: 'number',
2695
+ description: 'Staging site ID',
2696
+ },
2697
+ },
2698
+ required: ['productionSiteId', 'stagingSiteId'],
2699
+ },
2700
+ },
2701
+ {
2702
+ name: 'fcp_clone_status',
2703
+ description: 'Get the status of a clone-to-staging operation. Returns progress, step statuses (files, database, search/replace), and errors.',
2704
+ inputSchema: {
2705
+ type: 'object',
2706
+ properties: {
2707
+ cloneId: {
2708
+ type: 'string',
2709
+ description: 'Clone operation ID (e.g., clone_abc12345_lxyz)',
2710
+ },
2711
+ },
2712
+ required: ['cloneId'],
2713
+ },
2714
+ },
2715
+ {
2716
+ name: 'fcp_clone_list',
2717
+ description: 'List recent clone-to-staging operations for a site. Shows history of all clone operations with status.',
2718
+ inputSchema: {
2719
+ type: 'object',
2720
+ properties: {
2721
+ productionSiteId: {
2722
+ type: 'number',
2723
+ description: 'Filter by production site ID',
2724
+ },
2725
+ stagingSiteId: {
2726
+ type: 'number',
2727
+ description: 'Filter by staging site ID',
2728
+ },
2729
+ limit: {
2730
+ type: 'number',
2731
+ description: 'Max results (default: 20)',
2732
+ },
2733
+ },
2734
+ },
2735
+ },
2736
+ {
2737
+ name: 'fcp_get_dev_environment_info',
2738
+ description: 'Get developer environment info for a site. Shows what capabilities are available (clone to staging, download database, file sync, local setup), current state (latest backup age, clone status), staging sites, and quick action URLs. Use this to help developers understand what they can do with a site.',
2739
+ inputSchema: {
2740
+ type: 'object',
2741
+ properties: {
2742
+ siteId: {
2743
+ type: 'number',
2744
+ description: 'The production site ID to get dev environment info for',
2745
+ },
2746
+ },
2747
+ required: ['siteId'],
2748
+ },
2749
+ },
2587
2750
  ];
2588
2751
  // Register tool handlers
2589
2752
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -3664,6 +3827,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3664
3827
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3665
3828
  };
3666
3829
  }
3830
+ // Clone to Staging tools
3831
+ case 'fcp_clone_to_staging': {
3832
+ const { productionSiteId, stagingSiteId, includeFiles, includeDatabase, runSearchReplace, backupId } = args;
3833
+ // For MCP callers, auto-generate confirmation token (bypasses type-to-confirm UI)
3834
+ const confirmResult = await client.cloneToStagingConfirm(productionSiteId, stagingSiteId);
3835
+ const token = confirmResult.token;
3836
+ const result = await client.cloneToStaging({
3837
+ productionSiteId,
3838
+ stagingSiteId,
3839
+ includeFiles: includeFiles !== false,
3840
+ includeDatabase: includeDatabase !== false,
3841
+ runSearchReplace: runSearchReplace !== false,
3842
+ confirmationToken: token,
3843
+ backupId,
3844
+ });
3845
+ return {
3846
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3847
+ };
3848
+ }
3849
+ case 'fcp_clone_confirm': {
3850
+ const { productionSiteId, stagingSiteId } = args;
3851
+ const result = await client.cloneToStagingConfirm(productionSiteId, stagingSiteId);
3852
+ return {
3853
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3854
+ };
3855
+ }
3856
+ case 'fcp_clone_status': {
3857
+ const { cloneId } = args;
3858
+ const result = await client.getCloneStatus(cloneId);
3859
+ return {
3860
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3861
+ };
3862
+ }
3863
+ case 'fcp_clone_list': {
3864
+ const { productionSiteId, stagingSiteId, limit } = args;
3865
+ const result = await client.listCloneOperations({ productionSiteId, stagingSiteId, limit });
3866
+ return {
3867
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3868
+ };
3869
+ }
3870
+ case 'fcp_get_dev_environment_info': {
3871
+ const { siteId } = args;
3872
+ const result = await client.getDevEnvironmentInfo(siteId);
3873
+ return {
3874
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3875
+ };
3876
+ }
3667
3877
  default:
3668
3878
  throw new Error(`Unknown tool: ${name}`);
3669
3879
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fruition/fcp-mcp-server",
3
- "version": "1.15.0",
3
+ "version": "1.16.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",