@fruition/fcp-mcp-server 1.15.0 → 1.16.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/dist/index.js +230 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -41,6 +41,29 @@ 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
+ // Falls back to git config user.email for zero-config setup
47
+ function detectUserEmail() {
48
+ if (process.env.FCP_USER_EMAIL)
49
+ return process.env.FCP_USER_EMAIL;
50
+ try {
51
+ const email = execSync('git config user.email', {
52
+ encoding: 'utf-8',
53
+ timeout: 3000,
54
+ stdio: ['pipe', 'pipe', 'ignore'],
55
+ }).trim();
56
+ if (email) {
57
+ console.error(`[MCP Server] Auto-detected user email: ${email}`);
58
+ return email;
59
+ }
60
+ }
61
+ catch {
62
+ // Not in a git repo or no email configured
63
+ }
64
+ return '';
65
+ }
66
+ const FCP_USER_EMAIL = detectUserEmail();
44
67
  // If no Unroo key, use FCP as proxy (unified key setup)
45
68
  const USE_FCP_UNROO_PROXY = !UNROO_API_KEY;
46
69
  // Helper to check if Unroo functionality is available (either mode)
@@ -466,6 +489,35 @@ class FCPClient {
466
489
  body: JSON.stringify({ s3Key }),
467
490
  });
468
491
  }
492
+ // Clone to Staging
493
+ async cloneToStagingConfirm(productionSiteId, stagingSiteId) {
494
+ return this.fetch('/api/clone-staging/confirm', {
495
+ method: 'POST',
496
+ body: JSON.stringify({ productionSiteId, stagingSiteId }),
497
+ });
498
+ }
499
+ async cloneToStaging(params) {
500
+ return this.fetch('/api/clone-staging', {
501
+ method: 'POST',
502
+ body: JSON.stringify(params),
503
+ });
504
+ }
505
+ async getCloneStatus(cloneId) {
506
+ return this.fetch(`/api/clone-staging/${encodeURIComponent(cloneId)}`);
507
+ }
508
+ async listCloneOperations(params) {
509
+ const query = new URLSearchParams();
510
+ if (params?.productionSiteId)
511
+ query.set('productionSiteId', String(params.productionSiteId));
512
+ if (params?.stagingSiteId)
513
+ query.set('stagingSiteId', String(params.stagingSiteId));
514
+ if (params?.limit)
515
+ query.set('limit', String(params.limit));
516
+ return this.fetch(`/api/clone-staging?${query.toString()}`);
517
+ }
518
+ async getDevEnvironmentInfo(siteId) {
519
+ return this.fetch(`/api/sites/${siteId}/dev-info`);
520
+ }
469
521
  }
470
522
  // Unroo API Client
471
523
  // Supports two modes:
@@ -508,6 +560,11 @@ class UnrooClient {
508
560
  else if (this.fcpToken === 'dev_bypass') {
509
561
  headers['X-Dev-Bypass'] = 'true';
510
562
  }
563
+ // Pass actual user identity so Unroo attributes actions correctly
564
+ // (otherwise all actions show as the FCP API key creator)
565
+ if (FCP_USER_EMAIL) {
566
+ headers['X-Acting-User-Email'] = FCP_USER_EMAIL;
567
+ }
511
568
  }
512
569
  else {
513
570
  // Direct Unroo API call
@@ -1460,6 +1517,14 @@ const TOOLS = [
1460
1517
  type: 'string',
1461
1518
  description: 'Parent task ID if this is a subtask',
1462
1519
  },
1520
+ github_branch: {
1521
+ type: 'string',
1522
+ description: 'Git branch name associated with this task',
1523
+ },
1524
+ github_repo: {
1525
+ type: 'string',
1526
+ description: 'GitHub repository (owner/repo format)',
1527
+ },
1463
1528
  },
1464
1529
  required: ['title', 'project_key'],
1465
1530
  },
@@ -1517,6 +1582,22 @@ const TOOLS = [
1517
1582
  type: 'string',
1518
1583
  description: 'Resolution when marking as Done',
1519
1584
  },
1585
+ github_branch: {
1586
+ type: 'string',
1587
+ description: 'Git branch name associated with this task',
1588
+ },
1589
+ github_pr_url: {
1590
+ type: 'string',
1591
+ description: 'GitHub pull request URL',
1592
+ },
1593
+ github_pr_number: {
1594
+ type: 'number',
1595
+ description: 'GitHub pull request number',
1596
+ },
1597
+ github_repo: {
1598
+ type: 'string',
1599
+ description: 'GitHub repository (owner/repo format)',
1600
+ },
1520
1601
  },
1521
1602
  required: ['task_id'],
1522
1603
  },
@@ -2584,6 +2665,108 @@ const TOOLS = [
2584
2665
  required: ['s3Key'],
2585
2666
  },
2586
2667
  },
2668
+ // Clone to Staging (unified prod → staging with files + database + search/replace)
2669
+ {
2670
+ name: 'fcp_clone_to_staging',
2671
+ 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".',
2672
+ inputSchema: {
2673
+ type: 'object',
2674
+ properties: {
2675
+ productionSiteId: {
2676
+ type: 'number',
2677
+ description: 'Production site ID',
2678
+ },
2679
+ stagingSiteId: {
2680
+ type: 'number',
2681
+ description: 'Staging site ID to clone into',
2682
+ },
2683
+ includeFiles: {
2684
+ type: 'boolean',
2685
+ description: 'Whether to sync files from production snapshot (default: true)',
2686
+ },
2687
+ includeDatabase: {
2688
+ type: 'boolean',
2689
+ description: 'Whether to sync database from latest backup (default: true)',
2690
+ },
2691
+ runSearchReplace: {
2692
+ type: 'boolean',
2693
+ description: 'Whether to run domain search/replace after DB sync (default: true)',
2694
+ },
2695
+ backupId: {
2696
+ type: 'string',
2697
+ description: 'Specific backup ID to restore from (optional, defaults to latest)',
2698
+ },
2699
+ },
2700
+ required: ['productionSiteId', 'stagingSiteId'],
2701
+ },
2702
+ },
2703
+ {
2704
+ name: 'fcp_clone_confirm',
2705
+ description: 'Generate a safety confirmation token for a clone-to-staging operation. Returns token (5-min TTL) plus site info and latest backup age.',
2706
+ inputSchema: {
2707
+ type: 'object',
2708
+ properties: {
2709
+ productionSiteId: {
2710
+ type: 'number',
2711
+ description: 'Production site ID',
2712
+ },
2713
+ stagingSiteId: {
2714
+ type: 'number',
2715
+ description: 'Staging site ID',
2716
+ },
2717
+ },
2718
+ required: ['productionSiteId', 'stagingSiteId'],
2719
+ },
2720
+ },
2721
+ {
2722
+ name: 'fcp_clone_status',
2723
+ description: 'Get the status of a clone-to-staging operation. Returns progress, step statuses (files, database, search/replace), and errors.',
2724
+ inputSchema: {
2725
+ type: 'object',
2726
+ properties: {
2727
+ cloneId: {
2728
+ type: 'string',
2729
+ description: 'Clone operation ID (e.g., clone_abc12345_lxyz)',
2730
+ },
2731
+ },
2732
+ required: ['cloneId'],
2733
+ },
2734
+ },
2735
+ {
2736
+ name: 'fcp_clone_list',
2737
+ description: 'List recent clone-to-staging operations for a site. Shows history of all clone operations with status.',
2738
+ inputSchema: {
2739
+ type: 'object',
2740
+ properties: {
2741
+ productionSiteId: {
2742
+ type: 'number',
2743
+ description: 'Filter by production site ID',
2744
+ },
2745
+ stagingSiteId: {
2746
+ type: 'number',
2747
+ description: 'Filter by staging site ID',
2748
+ },
2749
+ limit: {
2750
+ type: 'number',
2751
+ description: 'Max results (default: 20)',
2752
+ },
2753
+ },
2754
+ },
2755
+ },
2756
+ {
2757
+ name: 'fcp_get_dev_environment_info',
2758
+ 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.',
2759
+ inputSchema: {
2760
+ type: 'object',
2761
+ properties: {
2762
+ siteId: {
2763
+ type: 'number',
2764
+ description: 'The production site ID to get dev environment info for',
2765
+ },
2766
+ },
2767
+ required: ['siteId'],
2768
+ },
2769
+ },
2587
2770
  ];
2588
2771
  // Register tool handlers
2589
2772
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -3664,6 +3847,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3664
3847
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3665
3848
  };
3666
3849
  }
3850
+ // Clone to Staging tools
3851
+ case 'fcp_clone_to_staging': {
3852
+ const { productionSiteId, stagingSiteId, includeFiles, includeDatabase, runSearchReplace, backupId } = args;
3853
+ // For MCP callers, auto-generate confirmation token (bypasses type-to-confirm UI)
3854
+ const confirmResult = await client.cloneToStagingConfirm(productionSiteId, stagingSiteId);
3855
+ const token = confirmResult.token;
3856
+ const result = await client.cloneToStaging({
3857
+ productionSiteId,
3858
+ stagingSiteId,
3859
+ includeFiles: includeFiles !== false,
3860
+ includeDatabase: includeDatabase !== false,
3861
+ runSearchReplace: runSearchReplace !== false,
3862
+ confirmationToken: token,
3863
+ backupId,
3864
+ });
3865
+ return {
3866
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3867
+ };
3868
+ }
3869
+ case 'fcp_clone_confirm': {
3870
+ const { productionSiteId, stagingSiteId } = args;
3871
+ const result = await client.cloneToStagingConfirm(productionSiteId, stagingSiteId);
3872
+ return {
3873
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3874
+ };
3875
+ }
3876
+ case 'fcp_clone_status': {
3877
+ const { cloneId } = args;
3878
+ const result = await client.getCloneStatus(cloneId);
3879
+ return {
3880
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3881
+ };
3882
+ }
3883
+ case 'fcp_clone_list': {
3884
+ const { productionSiteId, stagingSiteId, limit } = args;
3885
+ const result = await client.listCloneOperations({ productionSiteId, stagingSiteId, limit });
3886
+ return {
3887
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3888
+ };
3889
+ }
3890
+ case 'fcp_get_dev_environment_info': {
3891
+ const { siteId } = args;
3892
+ const result = await client.getDevEnvironmentInfo(siteId);
3893
+ return {
3894
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3895
+ };
3896
+ }
3667
3897
  default:
3668
3898
  throw new Error(`Unknown tool: ${name}`);
3669
3899
  }
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.1",
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",