@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.
- package/dist/index.js +230 -0
- 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.
|
|
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",
|