@fruition/fcp-mcp-server 1.14.2 → 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.
@@ -40,10 +40,12 @@ if [ -z "$UNROO_API_KEY" ]; then
40
40
  exit 0
41
41
  fi
42
42
 
43
- # Get git remote URL (if in a git repo)
43
+ # Get git remote URL and branch (if in a git repo)
44
44
  GIT_REMOTE=""
45
+ GIT_BRANCH=""
45
46
  if git rev-parse --git-dir > /dev/null 2>&1; then
46
47
  GIT_REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
48
+ GIT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
47
49
  fi
48
50
 
49
51
  # Get repo name from current directory
@@ -74,7 +76,8 @@ JSON_PAYLOAD=$(cat <<EOF
74
76
  "machine_id": "${MACHINE_ID}",
75
77
  "tool_name": "${TOOL_NAME:-}",
76
78
  "tool_description": "${TOOL_DESCRIPTION:-}",
77
- "project_slug": "${PROJECT_SLUG:-}"
79
+ "project_slug": "${PROJECT_SLUG:-}",
80
+ "git_branch": "${GIT_BRANCH}"
78
81
  }
79
82
  EOF
80
83
  )
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
@@ -591,6 +628,18 @@ class UnrooClient {
591
628
  });
592
629
  }
593
630
  // ============================================================================
631
+ // Comment APIs
632
+ // ============================================================================
633
+ async addComment(taskId, input) {
634
+ return this.fetch(`/api/external/fcp/tasks/${encodeURIComponent(taskId)}/comments`, {
635
+ method: 'POST',
636
+ body: JSON.stringify(input),
637
+ });
638
+ }
639
+ async listComments(taskId) {
640
+ return this.fetch(`/api/external/fcp/tasks/${encodeURIComponent(taskId)}/comments`);
641
+ }
642
+ // ============================================================================
594
643
  // Parking Lot / Backlog APIs
595
644
  // ============================================================================
596
645
  async logFutureWork(input) {
@@ -1448,6 +1497,14 @@ const TOOLS = [
1448
1497
  type: 'string',
1449
1498
  description: 'Parent task ID if this is a subtask',
1450
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
+ },
1451
1508
  },
1452
1509
  required: ['title', 'project_key'],
1453
1510
  },
@@ -1505,6 +1562,63 @@ const TOOLS = [
1505
1562
  type: 'string',
1506
1563
  description: 'Resolution when marking as Done',
1507
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
+ },
1581
+ },
1582
+ required: ['task_id'],
1583
+ },
1584
+ },
1585
+ {
1586
+ name: 'unroo_add_comment',
1587
+ description: 'Add a comment to an Unroo task. Use this for progress updates, notes, or tagging people with @mentions. Comments sync to Jira and notify watchers. Use is_internal=true for team-only notes.',
1588
+ inputSchema: {
1589
+ type: 'object',
1590
+ properties: {
1591
+ task_id: {
1592
+ type: 'string',
1593
+ description: 'Task ID or Jira key (e.g., task_abc123 or FLYFRU-189)',
1594
+ },
1595
+ comment_text: {
1596
+ type: 'string',
1597
+ description: 'The comment content. Use @email to mention people (e.g., @tony.diaz@fruition.net)',
1598
+ },
1599
+ is_internal: {
1600
+ type: 'boolean',
1601
+ description: 'If true, comment is team-only (not synced to Jira, not visible to customers). Default: false',
1602
+ },
1603
+ mentions: {
1604
+ type: 'array',
1605
+ items: { type: 'string' },
1606
+ description: 'Email addresses of people to mention/notify (e.g., ["tony.diaz@fruition.net"])',
1607
+ },
1608
+ },
1609
+ required: ['task_id', 'comment_text'],
1610
+ },
1611
+ },
1612
+ {
1613
+ name: 'unroo_list_comments',
1614
+ description: 'List comments on an Unroo task. Returns all comments in reverse chronological order.',
1615
+ inputSchema: {
1616
+ type: 'object',
1617
+ properties: {
1618
+ task_id: {
1619
+ type: 'string',
1620
+ description: 'Task ID or Jira key (e.g., task_abc123 or FLYFRU-189)',
1621
+ },
1508
1622
  },
1509
1623
  required: ['task_id'],
1510
1624
  },
@@ -2531,6 +2645,108 @@ const TOOLS = [
2531
2645
  required: ['s3Key'],
2532
2646
  },
2533
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
+ },
2534
2750
  ];
2535
2751
  // Register tool handlers
2536
2752
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -3061,6 +3277,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3061
3277
  ],
3062
3278
  };
3063
3279
  }
3280
+ case 'unroo_add_comment': {
3281
+ const { task_id, comment_text, is_internal, mentions } = args;
3282
+ const commentResult = await unrooClient.addComment(task_id, {
3283
+ comment_text,
3284
+ is_internal,
3285
+ mentions,
3286
+ });
3287
+ return {
3288
+ content: [
3289
+ {
3290
+ type: 'text',
3291
+ text: JSON.stringify({
3292
+ success: true,
3293
+ message: `Comment added to task ${task_id}${is_internal ? ' (internal)' : ''}`,
3294
+ comment: commentResult.comment,
3295
+ }, null, 2),
3296
+ },
3297
+ ],
3298
+ };
3299
+ }
3300
+ case 'unroo_list_comments': {
3301
+ const { task_id } = args;
3302
+ const commentsResult = await unrooClient.listComments(task_id);
3303
+ return {
3304
+ content: [
3305
+ {
3306
+ type: 'text',
3307
+ text: JSON.stringify({
3308
+ total: commentsResult.total,
3309
+ comments: commentsResult.comments,
3310
+ }, null, 2),
3311
+ },
3312
+ ],
3313
+ };
3314
+ }
3064
3315
  case 'unroo_get_my_tasks': {
3065
3316
  const { status, limit } = args;
3066
3317
  // Note: The API key determines the user, tasks are filtered server-side
@@ -3576,6 +3827,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3576
3827
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
3577
3828
  };
3578
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
+ }
3579
3877
  default:
3580
3878
  throw new Error(`Unknown tool: ${name}`);
3581
3879
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fruition/fcp-mcp-server",
3
- "version": "1.14.2",
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",