@fruition/fcp-mcp-server 1.25.0 → 1.26.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.
- package/dist/index.js +53 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -198,19 +198,19 @@ const TOOL_PERMISSIONS = {
|
|
|
198
198
|
fcp_get_bulk_notify_history: 'viewer',
|
|
199
199
|
};
|
|
200
200
|
// Resolved role for the current API key. Cached only on successful lookup so
|
|
201
|
-
// transient FCP outages don't pin us to
|
|
201
|
+
// transient FCP outages don't pin us to a failure for the rest of the session.
|
|
202
202
|
let cachedUserRole;
|
|
203
203
|
async function fetchUserRole() {
|
|
204
204
|
if (cachedUserRole !== undefined)
|
|
205
|
-
return cachedUserRole;
|
|
205
|
+
return { ok: true, role: cachedUserRole };
|
|
206
206
|
// dev_bypass is the local-dev token; treat as super_admin to avoid
|
|
207
207
|
// gating local development against role lookup.
|
|
208
208
|
if (FCP_API_TOKEN === 'dev_bypass') {
|
|
209
209
|
cachedUserRole = 'super_admin';
|
|
210
|
-
return
|
|
210
|
+
return { ok: true, role: 'super_admin' };
|
|
211
211
|
}
|
|
212
212
|
if (!FCP_API_TOKEN) {
|
|
213
|
-
return '
|
|
213
|
+
return { ok: false, kind: 'no_token' };
|
|
214
214
|
}
|
|
215
215
|
try {
|
|
216
216
|
const res = await fetch(`${FCP_API_URL}/api/mcp/me`, {
|
|
@@ -219,17 +219,20 @@ async function fetchUserRole() {
|
|
|
219
219
|
});
|
|
220
220
|
if (!res.ok) {
|
|
221
221
|
console.error(`[MCP Server] Role lookup failed: HTTP ${res.status}`);
|
|
222
|
-
|
|
222
|
+
// 401/403 => the API key itself is bad (invalid/expired/revoked).
|
|
223
|
+
// Everything else (5xx, 429, ...) is treated as a transient failure.
|
|
224
|
+
const kind = res.status === 401 || res.status === 403 ? 'auth' : 'transient';
|
|
225
|
+
return { ok: false, kind, status: res.status };
|
|
223
226
|
}
|
|
224
227
|
const data = await res.json();
|
|
225
228
|
const role = data?.role ?? 'none';
|
|
226
229
|
cachedUserRole = role;
|
|
227
230
|
console.error(`[MCP Server] Resolved user role: ${role} (${data?.email ?? 'unknown'})`);
|
|
228
|
-
return role;
|
|
231
|
+
return { ok: true, role };
|
|
229
232
|
}
|
|
230
233
|
catch (err) {
|
|
231
234
|
console.error('[MCP Server] Role lookup error:', err);
|
|
232
|
-
return '
|
|
235
|
+
return { ok: false, kind: 'transient' };
|
|
233
236
|
}
|
|
234
237
|
}
|
|
235
238
|
function isRoleAtLeast(actual, required) {
|
|
@@ -243,7 +246,26 @@ function isRoleAtLeast(actual, required) {
|
|
|
243
246
|
}
|
|
244
247
|
async function enforceToolPermission(toolName) {
|
|
245
248
|
const required = TOOL_PERMISSIONS[toolName] ?? 'viewer';
|
|
246
|
-
const
|
|
249
|
+
const lookup = await fetchUserRole();
|
|
250
|
+
// A failed lookup is NOT a role problem. Surface the real cause so the user
|
|
251
|
+
// takes the right action instead of asking a super_admin for a role they
|
|
252
|
+
// may already effectively have.
|
|
253
|
+
if (!lookup.ok) {
|
|
254
|
+
switch (lookup.kind) {
|
|
255
|
+
case 'no_token':
|
|
256
|
+
throw new Error('FCP_API_TOKEN is not set. Add your FCP API key to this MCP server\'s ' +
|
|
257
|
+
'env config (create one in FCP → Admin → API Keys).');
|
|
258
|
+
case 'auth':
|
|
259
|
+
throw new Error(`Your FCP API key is invalid, expired, or revoked (HTTP ${lookup.status}). ` +
|
|
260
|
+
'Create a new key in FCP → Admin → API Keys and update your MCP server ' +
|
|
261
|
+
'config. This is NOT a role/permission problem — do not request a role grant.');
|
|
262
|
+
case 'transient':
|
|
263
|
+
throw new Error('FCP role lookup is temporarily unavailable' +
|
|
264
|
+
(lookup.status ? ` (HTTP ${lookup.status})` : '') +
|
|
265
|
+
'. This is a transient FCP error — retry shortly.');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const actual = lookup.role;
|
|
247
269
|
if (!isRoleAtLeast(actual, required)) {
|
|
248
270
|
throw new Error(`Permission denied: tool '${toolName}' requires ${required} role; ` +
|
|
249
271
|
`your role is '${actual}'. Contact a super_admin (Brad, Mattox, or Andrea) ` +
|
|
@@ -2811,7 +2833,7 @@ const TOOLS = [
|
|
|
2811
2833
|
},
|
|
2812
2834
|
{
|
|
2813
2835
|
name: 'fcp_shield_add_domain',
|
|
2814
|
-
description: 'Add a domain to Fruition Shield WAF protection.
|
|
2836
|
+
description: 'Add a domain to Fruition Shield WAF protection. Custom mode (default) fronts an origin server via the shared distribution. S3 mode (origin_type="s3") provisions a DEDICATED CloudFront distribution + OAC over a private S3 bucket (Steldris static hosting) and returns its distribution id + CloudFront domain. Returns DNS records to add at the registrar.',
|
|
2815
2837
|
inputSchema: {
|
|
2816
2838
|
type: 'object',
|
|
2817
2839
|
properties: {
|
|
@@ -2819,9 +2841,14 @@ const TOOLS = [
|
|
|
2819
2841
|
type: 'string',
|
|
2820
2842
|
description: 'Domain to protect (e.g., www.butterflies.org)',
|
|
2821
2843
|
},
|
|
2844
|
+
origin_type: {
|
|
2845
|
+
type: 'string',
|
|
2846
|
+
enum: ['custom', 's3'],
|
|
2847
|
+
description: 'Origin mode: custom (shared distribution + origin server, default) or s3 (dedicated distribution + OAC over a private S3 bucket).',
|
|
2848
|
+
},
|
|
2822
2849
|
origin_host: {
|
|
2823
2850
|
type: 'string',
|
|
2824
|
-
description: 'Origin server IP or hostname (e.g., 146.190.2.169 for K8s prod)',
|
|
2851
|
+
description: 'Origin server IP or hostname (e.g., 146.190.2.169 for K8s prod). Required for origin_type=custom; ignored for s3.',
|
|
2825
2852
|
},
|
|
2826
2853
|
origin_port: {
|
|
2827
2854
|
type: 'number',
|
|
@@ -2831,6 +2858,18 @@ const TOOLS = [
|
|
|
2831
2858
|
type: 'string',
|
|
2832
2859
|
description: 'Origin protocol: http or https (default: https)',
|
|
2833
2860
|
},
|
|
2861
|
+
s3_bucket: {
|
|
2862
|
+
type: 'string',
|
|
2863
|
+
description: 'S3 artifacts bucket (origin_type=s3, e.g. steldris-sites-prod). Required for s3 mode.',
|
|
2864
|
+
},
|
|
2865
|
+
s3_region: {
|
|
2866
|
+
type: 'string',
|
|
2867
|
+
description: 'S3 bucket region (origin_type=s3, e.g. us-east-1). Required for s3 mode.',
|
|
2868
|
+
},
|
|
2869
|
+
s3_origin_path: {
|
|
2870
|
+
type: 'string',
|
|
2871
|
+
description: 'CloudFront OriginPath — the per-tenant key prefix (origin_type=s3, e.g. /acme).',
|
|
2872
|
+
},
|
|
2834
2873
|
website_id: {
|
|
2835
2874
|
type: 'number',
|
|
2836
2875
|
description: 'Link to existing FCP website ID',
|
|
@@ -2841,14 +2880,14 @@ const TOOLS = [
|
|
|
2841
2880
|
},
|
|
2842
2881
|
cache_profile: {
|
|
2843
2882
|
type: 'string',
|
|
2844
|
-
description: 'Cache profile: standard, aggressive, minimal, none (default: standard)',
|
|
2883
|
+
description: 'Cache profile: standard, aggressive, minimal, none (default: standard; s3 defaults to aggressive)',
|
|
2845
2884
|
},
|
|
2846
2885
|
notes: {
|
|
2847
2886
|
type: 'string',
|
|
2848
2887
|
description: 'Notes about this domain',
|
|
2849
2888
|
},
|
|
2850
2889
|
},
|
|
2851
|
-
required: ['domain'
|
|
2890
|
+
required: ['domain'],
|
|
2852
2891
|
},
|
|
2853
2892
|
},
|
|
2854
2893
|
{
|
|
@@ -4734,9 +4773,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4734
4773
|
};
|
|
4735
4774
|
}
|
|
4736
4775
|
case 'fcp_shield_add_domain': {
|
|
4737
|
-
const { domain, origin_host, origin_port, origin_protocol, website_id, account_id, cache_profile, notes } = args;
|
|
4776
|
+
const { domain, origin_type, origin_host, origin_port, origin_protocol, s3_bucket, s3_region, s3_origin_path, website_id, account_id, cache_profile, notes } = args;
|
|
4738
4777
|
const result = await client.shieldAddDomain({
|
|
4739
|
-
domain, origin_host, origin_port, origin_protocol, website_id, account_id, cache_profile, notes,
|
|
4778
|
+
domain, origin_type, origin_host, origin_port, origin_protocol, s3_bucket, s3_region, s3_origin_path, website_id, account_id, cache_profile, notes,
|
|
4740
4779
|
});
|
|
4741
4780
|
return {
|
|
4742
4781
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fruition/fcp-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.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",
|