@fruition/fcp-mcp-server 1.0.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/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # FCP MCP Server
2
+
3
+ MCP (Model Context Protocol) server that gives Claude Code direct access to the FCP Launch Coordination System.
4
+
5
+ ## Features
6
+
7
+ ### Tools
8
+
9
+ | Tool | Description |
10
+ |------|-------------|
11
+ | `fcp_list_launches` | List launches with optional filters (status, platform, upcoming days) |
12
+ | `fcp_get_launch` | Get detailed launch info including checklist and team |
13
+ | `fcp_get_legacy_access` | Get legacy hosting access info for migrations |
14
+ | `fcp_get_checklist` | Get checklist items with Claude instructions |
15
+ | `fcp_update_checklist_item` | Update checklist item status as you complete work |
16
+ | `fcp_add_progress_note` | Add progress notes to document work done |
17
+ | `fcp_get_claude_md` | Generate CLAUDE.md content for a launch |
18
+
19
+ ### Resources
20
+
21
+ - `fcp://launches` - List of all launches
22
+ - `fcp://launches/{id}` - Single launch details
23
+
24
+ ## Setup
25
+
26
+ ### 1. Install Dependencies
27
+
28
+ ```bash
29
+ cd mcp-server
30
+ npm install
31
+ ```
32
+
33
+ ### 2. Build
34
+
35
+ ```bash
36
+ npm run build
37
+ ```
38
+
39
+ ### 3. Configure Environment
40
+
41
+ Create a `.env` file or set environment variables:
42
+
43
+ ```bash
44
+ FCP_API_URL=https://fcp.fru.io # or http://localhost:3090 for local dev
45
+ FCP_API_TOKEN=your_api_token # Get from FCP Settings > API Tokens
46
+ ```
47
+
48
+ ### 4. Add to Claude Code
49
+
50
+ Add to your Claude Code settings (`~/.claude/settings.json`):
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "fcp": {
56
+ "command": "node",
57
+ "args": ["/path/to/fcp/mcp-server/dist/index.js"],
58
+ "env": {
59
+ "FCP_API_URL": "https://fcp.fru.io",
60
+ "FCP_API_TOKEN": "your_api_token"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Or for development:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "fcp": {
73
+ "command": "npx",
74
+ "args": ["tsx", "/path/to/fcp/mcp-server/src/index.ts"],
75
+ "env": {
76
+ "FCP_API_URL": "http://localhost:3090",
77
+ "FCP_API_TOKEN": "dev_token"
78
+ }
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Usage Examples
85
+
86
+ Once configured, Claude Code can use these tools naturally:
87
+
88
+ ### Get launch context for current project
89
+
90
+ ```
91
+ "What's the status of launch 3 and what checklist items are remaining?"
92
+ ```
93
+
94
+ Claude will call `fcp_get_launch` and `fcp_get_checklist` to get the info.
95
+
96
+ ### Work through checklist items
97
+
98
+ ```
99
+ "I've completed the database export. Mark that checklist item as done."
100
+ ```
101
+
102
+ Claude will call `fcp_update_checklist_item` to update the status.
103
+
104
+ ### Get legacy access info
105
+
106
+ ```
107
+ "How do I access the legacy hosting for this migration?"
108
+ ```
109
+
110
+ Claude will call `fcp_get_legacy_access` to get hosting, database, and file access details.
111
+
112
+ ### Document progress
113
+
114
+ ```
115
+ "Add a note that I've migrated all user accounts successfully."
116
+ ```
117
+
118
+ Claude will call `fcp_add_progress_note` to log the progress.
119
+
120
+ ## Development
121
+
122
+ Run in development mode:
123
+
124
+ ```bash
125
+ npm run dev
126
+ ```
127
+
128
+ This uses `tsx` to run TypeScript directly without building.
129
+
130
+ ## API Token
131
+
132
+ To get an API token for the MCP server:
133
+
134
+ 1. Go to FCP: https://fcp.fru.io/dashboard/settings
135
+ 2. Navigate to "API Tokens" section
136
+ 3. Create a new token with "Launch Management" permissions
137
+ 4. Copy the token to your MCP server configuration
138
+
139
+ ## Troubleshooting
140
+
141
+ ### "Authentication failed"
142
+ - Check that `FCP_API_TOKEN` is set correctly
143
+ - Verify the token has the required permissions
144
+
145
+ ### "Connection refused"
146
+ - Check that `FCP_API_URL` is correct
147
+ - For local dev, ensure FCP is running on port 3090
148
+
149
+ ### Tools not appearing
150
+ - Restart Claude Code after updating settings
151
+ - Check Claude Code logs for MCP server errors
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * FCP MCP Server
4
+ *
5
+ * Provides Claude Code with direct access to FCP Launch Coordination System:
6
+ * - Query launches, checklists, and legacy access info
7
+ * - Update checklist item status
8
+ * - Get project context for migrations
9
+ */
10
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,433 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * FCP MCP Server
4
+ *
5
+ * Provides Claude Code with direct access to FCP Launch Coordination System:
6
+ * - Query launches, checklists, and legacy access info
7
+ * - Update checklist item status
8
+ * - Get project context for migrations
9
+ */
10
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
13
+ // Configuration
14
+ const FCP_API_URL = process.env.FCP_API_URL || 'https://fcp.fru.io';
15
+ const FCP_API_TOKEN = process.env.FCP_API_TOKEN || '';
16
+ // API Client
17
+ class FCPClient {
18
+ baseUrl;
19
+ token;
20
+ constructor(baseUrl, token) {
21
+ this.baseUrl = baseUrl;
22
+ this.token = token;
23
+ }
24
+ async fetch(path, options = {}) {
25
+ const url = `${this.baseUrl}${path}`;
26
+ const headers = {
27
+ 'Content-Type': 'application/json',
28
+ ...(options.headers || {}),
29
+ };
30
+ if (this.token) {
31
+ // Support dev bypass mode for local testing
32
+ if (this.token === 'dev_bypass') {
33
+ headers['X-Dev-Bypass'] = 'true';
34
+ }
35
+ else {
36
+ // Production mode: use X-API-Key header
37
+ headers['X-API-Key'] = this.token;
38
+ }
39
+ }
40
+ const response = await fetch(url, {
41
+ ...options,
42
+ headers,
43
+ });
44
+ if (!response.ok) {
45
+ const error = await response.text();
46
+ throw new Error(`FCP API error (${response.status}): ${error}`);
47
+ }
48
+ return response.json();
49
+ }
50
+ async listLaunches(filters) {
51
+ const params = new URLSearchParams();
52
+ if (filters?.status)
53
+ params.set('status', filters.status);
54
+ if (filters?.platform)
55
+ params.set('platform', filters.platform);
56
+ if (filters?.upcoming)
57
+ params.set('upcoming', filters.upcoming.toString());
58
+ if (filters?.limit)
59
+ params.set('limit', filters.limit.toString());
60
+ const query = params.toString();
61
+ return this.fetch(`/api/launches${query ? `?${query}` : ''}`);
62
+ }
63
+ async getLaunch(id) {
64
+ return this.fetch(`/api/launches/${id}`);
65
+ }
66
+ async updateChecklistItem(launchId, itemId, updates) {
67
+ return this.fetch(`/api/launches/${launchId}/checklist/${itemId}`, {
68
+ method: 'PUT',
69
+ body: JSON.stringify(updates),
70
+ });
71
+ }
72
+ async getClaudeMd(launchId) {
73
+ return this.fetch(`/api/launches/${launchId}/claude-md`);
74
+ }
75
+ async addNote(launchId, content) {
76
+ return this.fetch(`/api/launches/${launchId}/notes`, {
77
+ method: 'POST',
78
+ body: JSON.stringify({ content }),
79
+ });
80
+ }
81
+ }
82
+ // Create server
83
+ const server = new Server({
84
+ name: 'fcp-mcp-server',
85
+ version: '1.0.0',
86
+ }, {
87
+ capabilities: {
88
+ tools: {},
89
+ resources: {},
90
+ },
91
+ });
92
+ const client = new FCPClient(FCP_API_URL, FCP_API_TOKEN);
93
+ // Tool definitions
94
+ const TOOLS = [
95
+ {
96
+ name: 'fcp_list_launches',
97
+ description: 'List launches from FCP with optional filters. Returns upcoming launches, their status, and basic info.',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ status: {
102
+ type: 'string',
103
+ description: 'Filter by status: planning, in_progress, soft_launched, launched, on_hold, cancelled',
104
+ },
105
+ platform: {
106
+ type: 'string',
107
+ description: 'Filter by platform: wordpress, drupal, nextjs, other',
108
+ },
109
+ upcoming: {
110
+ type: 'number',
111
+ description: 'Show launches within next N days',
112
+ },
113
+ limit: {
114
+ type: 'number',
115
+ description: 'Maximum number of launches to return',
116
+ },
117
+ },
118
+ },
119
+ },
120
+ {
121
+ name: 'fcp_get_launch',
122
+ description: 'Get detailed information about a specific launch including checklist items, legacy access info, and team assignments.',
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {
126
+ launch_id: {
127
+ type: 'number',
128
+ description: 'The ID of the launch to retrieve',
129
+ },
130
+ },
131
+ required: ['launch_id'],
132
+ },
133
+ },
134
+ {
135
+ name: 'fcp_get_legacy_access',
136
+ description: 'Get legacy hosting access information for a migration launch. Includes hosting provider, database access, file access, git repo, and DNS details.',
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {
140
+ launch_id: {
141
+ type: 'number',
142
+ description: 'The ID of the launch',
143
+ },
144
+ },
145
+ required: ['launch_id'],
146
+ },
147
+ },
148
+ {
149
+ name: 'fcp_get_checklist',
150
+ description: 'Get the launch checklist with item statuses and any Claude-specific instructions for completing tasks.',
151
+ inputSchema: {
152
+ type: 'object',
153
+ properties: {
154
+ launch_id: {
155
+ type: 'number',
156
+ description: 'The ID of the launch',
157
+ },
158
+ category: {
159
+ type: 'string',
160
+ description: 'Optional: filter by category (pre_launch, content, technical, seo, testing, dns, monitoring, post_launch)',
161
+ },
162
+ status: {
163
+ type: 'string',
164
+ description: 'Optional: filter by status (pending, in_progress, completed, blocked, skipped)',
165
+ },
166
+ },
167
+ required: ['launch_id'],
168
+ },
169
+ },
170
+ {
171
+ name: 'fcp_update_checklist_item',
172
+ description: 'Update the status of a checklist item. Use this to mark items as completed, in_progress, or blocked as you work through tasks.',
173
+ inputSchema: {
174
+ type: 'object',
175
+ properties: {
176
+ launch_id: {
177
+ type: 'number',
178
+ description: 'The ID of the launch',
179
+ },
180
+ item_id: {
181
+ type: 'number',
182
+ description: 'The ID of the checklist item',
183
+ },
184
+ status: {
185
+ type: 'string',
186
+ enum: ['pending', 'in_progress', 'completed', 'blocked', 'skipped'],
187
+ description: 'New status for the item',
188
+ },
189
+ notes: {
190
+ type: 'string',
191
+ description: 'Optional notes about the status change',
192
+ },
193
+ },
194
+ required: ['launch_id', 'item_id', 'status'],
195
+ },
196
+ },
197
+ {
198
+ name: 'fcp_add_progress_note',
199
+ description: 'Add a progress note to a launch. Use this to document what work was done or any issues encountered.',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {
203
+ launch_id: {
204
+ type: 'number',
205
+ description: 'The ID of the launch',
206
+ },
207
+ content: {
208
+ type: 'string',
209
+ description: 'The note content',
210
+ },
211
+ },
212
+ required: ['launch_id', 'content'],
213
+ },
214
+ },
215
+ {
216
+ name: 'fcp_get_claude_md',
217
+ description: 'Generate CLAUDE.md content for a launch. Returns formatted markdown with project context, legacy access info, and checklist.',
218
+ inputSchema: {
219
+ type: 'object',
220
+ properties: {
221
+ launch_id: {
222
+ type: 'number',
223
+ description: 'The ID of the launch',
224
+ },
225
+ },
226
+ required: ['launch_id'],
227
+ },
228
+ },
229
+ ];
230
+ // Register tool handlers
231
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
232
+ return { tools: TOOLS };
233
+ });
234
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
235
+ const { name, arguments: args } = request.params;
236
+ try {
237
+ switch (name) {
238
+ case 'fcp_list_launches': {
239
+ const result = await client.listLaunches(args);
240
+ return {
241
+ content: [
242
+ {
243
+ type: 'text',
244
+ text: JSON.stringify(result, null, 2),
245
+ },
246
+ ],
247
+ };
248
+ }
249
+ case 'fcp_get_launch': {
250
+ const { launch_id } = args;
251
+ const result = await client.getLaunch(launch_id);
252
+ return {
253
+ content: [
254
+ {
255
+ type: 'text',
256
+ text: JSON.stringify(result, null, 2),
257
+ },
258
+ ],
259
+ };
260
+ }
261
+ case 'fcp_get_legacy_access': {
262
+ const { launch_id } = args;
263
+ const result = await client.getLaunch(launch_id);
264
+ const legacyAccess = {
265
+ launch_id,
266
+ launch_name: result.launch.name,
267
+ launch_type: result.launch.launch_type,
268
+ legacy_domain: result.launch.legacy_domain,
269
+ legacy_access_info: result.launch.legacy_access_info || {},
270
+ };
271
+ return {
272
+ content: [
273
+ {
274
+ type: 'text',
275
+ text: JSON.stringify(legacyAccess, null, 2),
276
+ },
277
+ ],
278
+ };
279
+ }
280
+ case 'fcp_get_checklist': {
281
+ const { launch_id, category, status } = args;
282
+ const result = await client.getLaunch(launch_id);
283
+ let checklist = result.checklist || [];
284
+ if (category) {
285
+ checklist = checklist.filter((item) => item.category === category);
286
+ }
287
+ if (status) {
288
+ checklist = checklist.filter((item) => item.status === status);
289
+ }
290
+ // Format for readability
291
+ const formatted = checklist.map((item) => ({
292
+ id: item.id,
293
+ title: item.title,
294
+ status: item.status,
295
+ category: item.category,
296
+ is_blocker: item.is_blocker,
297
+ description: item.description,
298
+ claude_instructions: item.claude_instructions,
299
+ }));
300
+ return {
301
+ content: [
302
+ {
303
+ type: 'text',
304
+ text: JSON.stringify({
305
+ launch_id,
306
+ launch_name: result.launch.name,
307
+ total_items: formatted.length,
308
+ completed: formatted.filter((i) => i.status === 'completed').length,
309
+ blockers: formatted.filter((i) => i.is_blocker && i.status !== 'completed').length,
310
+ items: formatted,
311
+ }, null, 2),
312
+ },
313
+ ],
314
+ };
315
+ }
316
+ case 'fcp_update_checklist_item': {
317
+ const { launch_id, item_id, status, notes } = args;
318
+ const result = await client.updateChecklistItem(launch_id, item_id, {
319
+ status,
320
+ notes,
321
+ });
322
+ return {
323
+ content: [
324
+ {
325
+ type: 'text',
326
+ text: JSON.stringify({
327
+ success: true,
328
+ message: `Checklist item ${item_id} updated to status: ${status}`,
329
+ item: result,
330
+ }, null, 2),
331
+ },
332
+ ],
333
+ };
334
+ }
335
+ case 'fcp_add_progress_note': {
336
+ const { launch_id, content } = args;
337
+ const result = await client.addNote(launch_id, content);
338
+ return {
339
+ content: [
340
+ {
341
+ type: 'text',
342
+ text: JSON.stringify({
343
+ success: true,
344
+ message: 'Progress note added',
345
+ note: result,
346
+ }, null, 2),
347
+ },
348
+ ],
349
+ };
350
+ }
351
+ case 'fcp_get_claude_md': {
352
+ const { launch_id } = args;
353
+ const result = await client.getClaudeMd(launch_id);
354
+ return {
355
+ content: [
356
+ {
357
+ type: 'text',
358
+ text: result.content,
359
+ },
360
+ ],
361
+ };
362
+ }
363
+ default:
364
+ throw new Error(`Unknown tool: ${name}`);
365
+ }
366
+ }
367
+ catch (error) {
368
+ const message = error instanceof Error ? error.message : String(error);
369
+ return {
370
+ content: [
371
+ {
372
+ type: 'text',
373
+ text: JSON.stringify({ error: message }, null, 2),
374
+ },
375
+ ],
376
+ isError: true,
377
+ };
378
+ }
379
+ });
380
+ // Resource handlers
381
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
382
+ return {
383
+ resources: [
384
+ {
385
+ uri: 'fcp://launches',
386
+ name: 'FCP Launches',
387
+ description: 'List of all launches in FCP',
388
+ mimeType: 'application/json',
389
+ },
390
+ ],
391
+ };
392
+ });
393
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
394
+ const { uri } = request.params;
395
+ if (uri === 'fcp://launches') {
396
+ const result = await client.listLaunches({ limit: 50 });
397
+ return {
398
+ contents: [
399
+ {
400
+ uri,
401
+ mimeType: 'application/json',
402
+ text: JSON.stringify(result, null, 2),
403
+ },
404
+ ],
405
+ };
406
+ }
407
+ // Handle fcp://launches/{id}
408
+ const launchMatch = uri.match(/^fcp:\/\/launches\/(\d+)$/);
409
+ if (launchMatch) {
410
+ const launchId = parseInt(launchMatch[1], 10);
411
+ const result = await client.getLaunch(launchId);
412
+ return {
413
+ contents: [
414
+ {
415
+ uri,
416
+ mimeType: 'application/json',
417
+ text: JSON.stringify(result, null, 2),
418
+ },
419
+ ],
420
+ };
421
+ }
422
+ throw new Error(`Unknown resource: ${uri}`);
423
+ });
424
+ // Start server
425
+ async function main() {
426
+ const transport = new StdioServerTransport();
427
+ await server.connect(transport);
428
+ console.error('FCP MCP Server running on stdio');
429
+ }
430
+ main().catch((error) => {
431
+ console.error('Fatal error:', error);
432
+ process.exit(1);
433
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@fruition/fcp-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server for FCP Launch Coordination System - enables Claude Code to interact with FCP launches",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "fcp-mcp-server": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "tsx src/index.ts",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "claude",
22
+ "fcp",
23
+ "launch-coordination",
24
+ "anthropic"
25
+ ],
26
+ "author": "Fruition",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/fruition/fcp.git",
31
+ "directory": "mcp-server"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.0.0",
41
+ "tsx": "^4.7.0",
42
+ "typescript": "^5.3.0"
43
+ }
44
+ }