@fruition/fcp-mcp-server 1.3.0 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +14 -1
  2. package/dist/index.js +455 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@ MCP (Model Context Protocol) server that gives Claude Code direct access to the
4
4
 
5
5
  ## Features
6
6
 
7
- ### Tools
7
+ ### FCP Launch Management Tools
8
8
 
9
9
  | Tool | Description |
10
10
  |------|-------------|
@@ -16,6 +16,19 @@ MCP (Model Context Protocol) server that gives Claude Code direct access to the
16
16
  | `fcp_add_progress_note` | Add progress notes to document work done |
17
17
  | `fcp_get_claude_md` | Generate CLAUDE.md content for a launch |
18
18
 
19
+ ### Unroo Task Management Tools
20
+
21
+ | Tool | Description |
22
+ |------|-------------|
23
+ | `unroo_list_projects` | List all Unroo projects mapped to FCP clients |
24
+ | `unroo_list_tasks` | Query tasks with filters (status, project, assignee) |
25
+ | `unroo_create_task` | Create new tasks for discovered issues or follow-ups |
26
+ | `unroo_update_task` | Update task status, hours logged, priority |
27
+ | `unroo_get_my_tasks` | Get tasks assigned to current user |
28
+ | `unroo_start_session` | Start work session for time tracking |
29
+ | `unroo_end_session` | End work session and log time |
30
+ | `unroo_create_follow_up` | Create follow-up task linked to a parent |
31
+
19
32
  ### Resources
20
33
 
21
34
  - `fcp://launches` - List of all launches
package/dist/index.js CHANGED
@@ -33,20 +33,104 @@ catch {
33
33
  console.error('[MCP Server] Warning: Could not read package.json version');
34
34
  }
35
35
  }
36
+ import { execSync } from 'child_process';
36
37
  // Configuration
37
38
  const FCP_API_URL = process.env.FCP_API_URL || 'https://fcp.fru.io';
38
39
  const FCP_API_TOKEN = process.env.FCP_API_TOKEN || '';
40
+ // Unroo API can be called directly (legacy) or proxied through FCP (recommended)
41
+ // When UNROO_API_KEY is not set, Unroo calls go through FCP's proxy at /api/mcp/unroo/*
39
42
  const UNROO_API_URL = process.env.UNROO_API_URL || 'https://chat.frugpt.com';
40
43
  const UNROO_API_KEY = process.env.UNROO_API_KEY || '';
44
+ // If no Unroo key, use FCP as proxy (unified key setup)
45
+ const USE_FCP_UNROO_PROXY = !UNROO_API_KEY;
46
+ // Helper to check if Unroo functionality is available (either mode)
47
+ const UNROO_AVAILABLE = UNROO_API_KEY || (USE_FCP_UNROO_PROXY && FCP_API_TOKEN);
48
+ let currentProject = null;
49
+ /**
50
+ * Detect the current git remote URL
51
+ */
52
+ function detectGitRemote() {
53
+ try {
54
+ const remote = execSync('git config --get remote.origin.url', {
55
+ encoding: 'utf-8',
56
+ timeout: 5000,
57
+ stdio: ['pipe', 'pipe', 'ignore'],
58
+ }).trim();
59
+ return remote || null;
60
+ }
61
+ catch {
62
+ // Not a git repo or no remote configured
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * Resolve project from git remote URL via FCP API
68
+ */
69
+ async function resolveProjectFromRepo(repoUrl) {
70
+ if (!FCP_API_URL) {
71
+ return null;
72
+ }
73
+ try {
74
+ const url = `${FCP_API_URL}/api/mcp/resolve-project?repo_url=${encodeURIComponent(repoUrl)}`;
75
+ const headers = {
76
+ 'Content-Type': 'application/json',
77
+ };
78
+ if (FCP_API_TOKEN && FCP_API_TOKEN !== 'dev_bypass') {
79
+ headers['X-API-Key'] = FCP_API_TOKEN;
80
+ }
81
+ else if (FCP_API_TOKEN === 'dev_bypass') {
82
+ headers['X-Dev-Bypass'] = 'true';
83
+ }
84
+ const response = await fetch(url, { headers });
85
+ if (!response.ok) {
86
+ return null;
87
+ }
88
+ const data = await response.json();
89
+ if (data.found && data.project) {
90
+ return {
91
+ project_key: data.project.project_key,
92
+ website_id: data.project.website_id,
93
+ domain: data.project.domain,
94
+ account_name: data.project.account_name || 'Unknown',
95
+ github: data.project.github,
96
+ };
97
+ }
98
+ return null;
99
+ }
100
+ catch (error) {
101
+ console.error('[MCP Server] Error resolving project:', error);
102
+ return null;
103
+ }
104
+ }
105
+ /**
106
+ * Initialize project detection on startup
107
+ */
108
+ async function initializeProjectDetection() {
109
+ const remoteUrl = detectGitRemote();
110
+ if (!remoteUrl) {
111
+ console.error('[MCP Server] No git remote detected - project will be tracked as general');
112
+ return;
113
+ }
114
+ console.error(`[MCP Server] Detected git remote: ${remoteUrl}`);
115
+ const project = await resolveProjectFromRepo(remoteUrl);
116
+ if (project) {
117
+ currentProject = project;
118
+ console.error(`[MCP Server] Resolved project: ${project.domain} (${project.project_key})`);
119
+ }
120
+ else {
121
+ console.error('[MCP Server] Could not resolve project from repo - check FCP website github config');
122
+ }
123
+ }
41
124
  // API Client
42
125
  class FCPClient {
43
126
  baseUrl;
44
127
  token;
128
+ defaultTimeout = 15000; // 15 seconds
45
129
  constructor(baseUrl, token) {
46
130
  this.baseUrl = baseUrl;
47
131
  this.token = token;
48
132
  }
49
- async fetch(path, options = {}) {
133
+ async fetch(path, options = {}, timeoutMs) {
50
134
  const url = `${this.baseUrl}${path}`;
51
135
  const headers = {
52
136
  'Content-Type': 'application/json',
@@ -65,6 +149,7 @@ class FCPClient {
65
149
  const response = await fetch(url, {
66
150
  ...options,
67
151
  headers,
152
+ signal: AbortSignal.timeout(timeoutMs || this.defaultTimeout),
68
153
  });
69
154
  if (!response.ok) {
70
155
  const error = await response.text();
@@ -105,27 +190,61 @@ class FCPClient {
105
190
  }
106
191
  }
107
192
  // Unroo API Client
193
+ // Supports two modes:
194
+ // 1. Direct: Uses UNROO_API_KEY to call Unroo directly (legacy)
195
+ // 2. Proxy: Routes through FCP at /api/mcp/unroo/* using FCP_API_TOKEN (recommended)
108
196
  class UnrooClient {
109
197
  baseUrl;
110
198
  apiKey;
111
- constructor(baseUrl, apiKey) {
199
+ useProxy;
200
+ fcpUrl;
201
+ fcpToken;
202
+ defaultTimeout = 15000; // 15 seconds
203
+ constructor(baseUrl, apiKey, useProxy = false, fcpUrl = '', fcpToken = '') {
112
204
  this.baseUrl = baseUrl;
113
205
  this.apiKey = apiKey;
206
+ this.useProxy = useProxy;
207
+ this.fcpUrl = fcpUrl;
208
+ this.fcpToken = fcpToken;
209
+ if (this.useProxy) {
210
+ console.error('[UnrooClient] Using FCP proxy mode (unified key)');
211
+ }
212
+ else {
213
+ console.error('[UnrooClient] Using direct Unroo API mode');
214
+ }
114
215
  }
115
- async fetch(path, options = {}) {
116
- const url = `${this.baseUrl}${path}`;
216
+ async fetch(path, options = {}, timeoutMs) {
217
+ let url;
117
218
  const headers = {
118
219
  'Content-Type': 'application/json',
119
- 'X-API-Key': this.apiKey,
120
220
  ...(options.headers || {}),
121
221
  };
222
+ if (this.useProxy) {
223
+ // Route through FCP proxy: /api/external/fcp/X -> /api/mcp/unroo/X
224
+ const proxyPath = path.replace('/api/external/fcp/', '/api/mcp/unroo/');
225
+ url = `${this.fcpUrl}${proxyPath}`;
226
+ // Use FCP API token
227
+ if (this.fcpToken && this.fcpToken !== 'dev_bypass') {
228
+ headers['X-API-Key'] = this.fcpToken;
229
+ }
230
+ else if (this.fcpToken === 'dev_bypass') {
231
+ headers['X-Dev-Bypass'] = 'true';
232
+ }
233
+ }
234
+ else {
235
+ // Direct Unroo API call
236
+ url = `${this.baseUrl}${path}`;
237
+ headers['X-API-Key'] = this.apiKey;
238
+ }
122
239
  const response = await fetch(url, {
123
240
  ...options,
124
241
  headers,
242
+ signal: AbortSignal.timeout(timeoutMs || this.defaultTimeout),
125
243
  });
126
244
  if (!response.ok) {
127
245
  const error = await response.text();
128
- throw new Error(`Unroo API error (${response.status}): ${error}`);
246
+ const source = this.useProxy ? 'FCP Proxy' : 'Unroo API';
247
+ throw new Error(`${source} error (${response.status}): ${error}`);
129
248
  }
130
249
  return response.json();
131
250
  }
@@ -180,6 +299,64 @@ class UnrooClient {
180
299
  body: JSON.stringify({ action: 'heartbeat', ...input }),
181
300
  });
182
301
  }
302
+ async logActivity(taskId, input) {
303
+ return this.fetch(`/api/external/fcp/tasks/${encodeURIComponent(taskId)}/activity`, {
304
+ method: 'POST',
305
+ body: JSON.stringify({
306
+ activity_type: input.activity_type,
307
+ source: input.source || 'claude_code',
308
+ field_changed: input.field_changed,
309
+ old_value: input.old_value,
310
+ new_value: input.new_value,
311
+ metadata: input.metadata,
312
+ }),
313
+ });
314
+ }
315
+ // ============================================================================
316
+ // Parking Lot / Backlog APIs
317
+ // ============================================================================
318
+ async logFutureWork(input) {
319
+ return this.fetch('/api/external/fcp/future-work', {
320
+ method: 'POST',
321
+ body: JSON.stringify(input),
322
+ });
323
+ }
324
+ async getFutureWork(filters) {
325
+ const params = new URLSearchParams();
326
+ if (filters.project_key)
327
+ params.set('project_key', filters.project_key);
328
+ if (filters.account_id)
329
+ params.set('account_id', filters.account_id);
330
+ if (filters.status)
331
+ params.set('status', filters.status);
332
+ if (filters.destination)
333
+ params.set('destination', filters.destination);
334
+ if (filters.limit)
335
+ params.set('limit', filters.limit.toString());
336
+ if (filters.offset)
337
+ params.set('offset', filters.offset.toString());
338
+ const query = params.toString();
339
+ return this.fetch(`/api/external/fcp/future-work${query ? `?${query}` : ''}`);
340
+ }
341
+ async updateFutureWork(id, updates) {
342
+ return this.fetch(`/api/external/fcp/future-work/${encodeURIComponent(id)}`, {
343
+ method: 'PUT',
344
+ body: JSON.stringify(updates),
345
+ });
346
+ }
347
+ async getBacklog(filters) {
348
+ const params = new URLSearchParams();
349
+ if (filters.project_key)
350
+ params.set('project_key', filters.project_key);
351
+ if (filters.priority)
352
+ params.set('priority', filters.priority);
353
+ if (filters.limit)
354
+ params.set('limit', filters.limit.toString());
355
+ if (filters.offset)
356
+ params.set('offset', filters.offset.toString());
357
+ const query = params.toString();
358
+ return this.fetch(`/api/external/fcp/backlog${query ? `?${query}` : ''}`);
359
+ }
183
360
  }
184
361
  // ============================================================================
185
362
  // Auto Session Tracking
@@ -192,6 +369,8 @@ class SessionTracker {
192
369
  activities = [];
193
370
  heartbeatInterval = null;
194
371
  currentTaskId = null;
372
+ consecutiveHeartbeatFailures = 0;
373
+ static MAX_HEARTBEAT_FAILURES = 3;
195
374
  constructor(unrooClient) {
196
375
  this.unrooClient = unrooClient;
197
376
  }
@@ -210,7 +389,8 @@ class SessionTracker {
210
389
  this.activities = this.activities.slice(-50);
211
390
  }
212
391
  // Auto-start session on first tool call
213
- if (!this.sessionActive && UNROO_API_KEY) {
392
+ // Session tracking works with either direct Unroo key OR FCP proxy mode
393
+ if (!this.sessionActive && UNROO_AVAILABLE) {
214
394
  await this.startSession();
215
395
  }
216
396
  // Send heartbeat every 30 tool calls or every 5 minutes
@@ -230,22 +410,39 @@ class SessionTracker {
230
410
  * Start a new session
231
411
  */
232
412
  async startSession() {
233
- if (!UNROO_API_KEY) {
413
+ if (!UNROO_AVAILABLE) {
234
414
  return;
235
415
  }
236
416
  try {
237
- await this.unrooClient.startSession({
417
+ // Use auto-detected project if available
418
+ const sessionInput = {
238
419
  task_id: this.currentTaskId || undefined,
239
420
  source: 'claude-code-mcp',
240
421
  machine_id: process.env.HOSTNAME || 'unknown',
241
- });
422
+ };
423
+ // Add auto-detected project info
424
+ if (currentProject) {
425
+ sessionInput.project_key = currentProject.project_key;
426
+ sessionInput.repo_name = `${currentProject.github.owner}/${currentProject.github.repo}`;
427
+ }
428
+ await this.unrooClient.startSession(sessionInput);
242
429
  this.sessionActive = true;
243
430
  this.lastHeartbeat = new Date();
244
431
  // Set up periodic heartbeat (every 5 minutes)
245
432
  this.heartbeatInterval = setInterval(() => {
246
- this.sendHeartbeat().catch(() => { });
433
+ this.sendHeartbeat().catch((err) => {
434
+ this.consecutiveHeartbeatFailures++;
435
+ if (this.consecutiveHeartbeatFailures >= SessionTracker.MAX_HEARTBEAT_FAILURES) {
436
+ console.error(`[SessionTracker] Heartbeat failed ${this.consecutiveHeartbeatFailures}x - session tracking may not work`);
437
+ }
438
+ });
247
439
  }, 5 * 60 * 1000);
248
- console.error('[SessionTracker] Session started');
440
+ if (currentProject) {
441
+ console.error(`[SessionTracker] Session started for project: ${currentProject.domain}`);
442
+ }
443
+ else {
444
+ console.error('[SessionTracker] Session started (no project detected)');
445
+ }
249
446
  }
250
447
  catch (error) {
251
448
  console.error('[SessionTracker] Failed to start session:', error);
@@ -255,7 +452,7 @@ class SessionTracker {
255
452
  * Send a heartbeat to keep session alive
256
453
  */
257
454
  async sendHeartbeat() {
258
- if (!this.sessionActive || !UNROO_API_KEY) {
455
+ if (!this.sessionActive || !UNROO_AVAILABLE) {
259
456
  return;
260
457
  }
261
458
  try {
@@ -265,17 +462,20 @@ class SessionTracker {
265
462
  });
266
463
  this.lastHeartbeat = new Date();
267
464
  this.toolCallCount = 0;
465
+ this.consecutiveHeartbeatFailures = 0; // Reset on success
268
466
  console.error('[SessionTracker] Heartbeat sent');
269
467
  }
270
468
  catch (error) {
271
- console.error('[SessionTracker] Failed to send heartbeat:', error);
469
+ this.consecutiveHeartbeatFailures++;
470
+ console.error(`[SessionTracker] Failed to send heartbeat (attempt ${this.consecutiveHeartbeatFailures}):`, error);
471
+ throw error; // Re-throw for interval handler
272
472
  }
273
473
  }
274
474
  /**
275
- * End the current session
475
+ * End the current session and log activity summary to task
276
476
  */
277
477
  async endSession() {
278
- if (!this.sessionActive || !UNROO_API_KEY) {
478
+ if (!this.sessionActive || !UNROO_AVAILABLE) {
279
479
  return;
280
480
  }
281
481
  if (this.heartbeatInterval) {
@@ -283,9 +483,34 @@ class SessionTracker {
283
483
  this.heartbeatInterval = null;
284
484
  }
285
485
  try {
286
- await this.unrooClient.endSession();
486
+ // End the session first
487
+ const result = await this.unrooClient.endSession();
287
488
  this.sessionActive = false;
288
489
  console.error('[SessionTracker] Session ended');
490
+ // Log activity summary to the task if we have a current task
491
+ if (this.currentTaskId && result.session) {
492
+ try {
493
+ const session = result.session;
494
+ const uniqueTools = [...new Set(this.activities.map(a => a.tool))];
495
+ await this.unrooClient.logActivity(this.currentTaskId, {
496
+ activity_type: 'claude_code_session',
497
+ source: 'claude_code',
498
+ metadata: {
499
+ session_id: session.id,
500
+ duration_seconds: session.duration_minutes ? session.duration_minutes * 60 : 0,
501
+ total_tool_calls: session.total_tool_calls || this.toolCallCount,
502
+ tools_used: uniqueTools.slice(0, 20), // Limit to 20 tools
503
+ started_at: session.started_at,
504
+ ended_at: session.ended_at || new Date().toISOString(),
505
+ },
506
+ });
507
+ console.error(`[SessionTracker] Logged session activity to task ${this.currentTaskId}`);
508
+ }
509
+ catch (activityError) {
510
+ console.error('[SessionTracker] Failed to log activity to task:', activityError);
511
+ // Don't throw - session end was successful
512
+ }
513
+ }
289
514
  }
290
515
  catch (error) {
291
516
  console.error('[SessionTracker] Failed to end session:', error);
@@ -314,7 +539,7 @@ const server = new Server({
314
539
  },
315
540
  });
316
541
  const client = new FCPClient(FCP_API_URL, FCP_API_TOKEN);
317
- const unrooClient = new UnrooClient(UNROO_API_URL, UNROO_API_KEY);
542
+ const unrooClient = new UnrooClient(UNROO_API_URL, UNROO_API_KEY, USE_FCP_UNROO_PROXY, FCP_API_URL, FCP_API_TOKEN);
318
543
  const sessionTracker = new SessionTracker(unrooClient);
319
544
  // Tool definitions
320
545
  const TOOLS = [
@@ -662,6 +887,126 @@ const TOOLS = [
662
887
  required: ['parent_task_id', 'title'],
663
888
  },
664
889
  },
890
+ // Parking Lot / Backlog Tools
891
+ {
892
+ name: 'unroo_log_future_work',
893
+ description: 'Log future work items to parking lot or backlog. Use when you discover work that should be done later - bugs, tech debt, features, documentation needs, etc.',
894
+ inputSchema: {
895
+ type: 'object',
896
+ properties: {
897
+ title: {
898
+ type: 'string',
899
+ description: 'Title of the future work item (required)',
900
+ },
901
+ project_key: {
902
+ type: 'string',
903
+ description: 'JIRA project key or FCP-SITE-{id} (required)',
904
+ },
905
+ description: {
906
+ type: 'string',
907
+ description: 'Detailed description of the work needed',
908
+ },
909
+ priority: {
910
+ type: 'string',
911
+ enum: ['Urgent', 'High', 'Medium', 'Low'],
912
+ description: 'Priority level (default: Medium)',
913
+ },
914
+ task_type: {
915
+ type: 'string',
916
+ enum: ['bug', 'tech_debt', 'feature', 'documentation', 'security', 'performance'],
917
+ description: 'Type of work item',
918
+ },
919
+ estimated_hours: {
920
+ type: 'number',
921
+ description: 'Estimated hours to complete',
922
+ },
923
+ launch_id: {
924
+ type: 'number',
925
+ description: 'FCP launch ID if related to a launch',
926
+ },
927
+ checklist_item_id: {
928
+ type: 'number',
929
+ description: 'FCP checklist item ID if discovered during checklist work',
930
+ },
931
+ notes: {
932
+ type: 'string',
933
+ description: 'Additional notes or context',
934
+ },
935
+ destination: {
936
+ type: 'string',
937
+ enum: ['parking_lot', 'backlog'],
938
+ description: 'Where to put the item: parking_lot (needs review) or backlog (ready for sprint). Default: parking_lot',
939
+ },
940
+ },
941
+ required: ['title', 'project_key'],
942
+ },
943
+ },
944
+ {
945
+ name: 'unroo_get_parking_lot',
946
+ description: 'Get items from the parking lot - discovered work that needs review before being added to backlog.',
947
+ inputSchema: {
948
+ type: 'object',
949
+ properties: {
950
+ project_key: {
951
+ type: 'string',
952
+ description: 'Filter by JIRA project key or FCP-SITE-{id}',
953
+ },
954
+ status: {
955
+ type: 'string',
956
+ description: 'Filter by status: pending, approved, rejected, converted (default: pending)',
957
+ },
958
+ limit: {
959
+ type: 'number',
960
+ description: 'Maximum number of items to return (default: 100)',
961
+ },
962
+ },
963
+ },
964
+ },
965
+ {
966
+ name: 'unroo_get_backlog',
967
+ description: 'Get backlog items - tasks ready to be scheduled into sprints.',
968
+ inputSchema: {
969
+ type: 'object',
970
+ properties: {
971
+ project_key: {
972
+ type: 'string',
973
+ description: 'Filter by JIRA project key or FCP-SITE-{id}',
974
+ },
975
+ priority: {
976
+ type: 'string',
977
+ enum: ['Urgent', 'High', 'Medium', 'Low'],
978
+ description: 'Filter by priority',
979
+ },
980
+ limit: {
981
+ type: 'number',
982
+ description: 'Maximum number of items to return (default: 100)',
983
+ },
984
+ },
985
+ },
986
+ },
987
+ {
988
+ name: 'unroo_convert_to_backlog',
989
+ description: 'Convert a parking lot item to backlog. Use after reviewing a discovered item and deciding it should be done.',
990
+ inputSchema: {
991
+ type: 'object',
992
+ properties: {
993
+ id: {
994
+ type: 'string',
995
+ description: 'The ID of the parking lot item to convert (required)',
996
+ },
997
+ priority: {
998
+ type: 'string',
999
+ enum: ['Urgent', 'High', 'Medium', 'Low'],
1000
+ description: 'Priority for the backlog item (optional, keeps original if not specified)',
1001
+ },
1002
+ notes: {
1003
+ type: 'string',
1004
+ description: 'Notes about the conversion decision',
1005
+ },
1006
+ },
1007
+ required: ['id'],
1008
+ },
1009
+ },
665
1010
  ];
666
1011
  // Register tool handlers
667
1012
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -952,6 +1297,89 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
952
1297
  ],
953
1298
  };
954
1299
  }
1300
+ // Parking Lot / Backlog Handlers
1301
+ case 'unroo_log_future_work': {
1302
+ const input = args;
1303
+ const result = await unrooClient.logFutureWork({
1304
+ ...input,
1305
+ discovered_by: 'claude-code-mcp',
1306
+ });
1307
+ return {
1308
+ content: [
1309
+ {
1310
+ type: 'text',
1311
+ text: JSON.stringify({
1312
+ success: true,
1313
+ message: result.message,
1314
+ id: result.id,
1315
+ destination: result.destination,
1316
+ }, null, 2),
1317
+ },
1318
+ ],
1319
+ };
1320
+ }
1321
+ case 'unroo_get_parking_lot': {
1322
+ const { project_key, status, limit } = args;
1323
+ const result = await unrooClient.getFutureWork({
1324
+ project_key,
1325
+ status: status || 'pending',
1326
+ destination: 'parking_lot',
1327
+ limit: limit || 100,
1328
+ });
1329
+ return {
1330
+ content: [
1331
+ {
1332
+ type: 'text',
1333
+ text: JSON.stringify({
1334
+ success: true,
1335
+ total: result.items.length,
1336
+ stats: result.stats,
1337
+ items: result.items,
1338
+ }, null, 2),
1339
+ },
1340
+ ],
1341
+ };
1342
+ }
1343
+ case 'unroo_get_backlog': {
1344
+ const { project_key, priority, limit } = args;
1345
+ const result = await unrooClient.getBacklog({
1346
+ project_key,
1347
+ priority,
1348
+ limit: limit || 100,
1349
+ });
1350
+ return {
1351
+ content: [
1352
+ {
1353
+ type: 'text',
1354
+ text: JSON.stringify({
1355
+ success: true,
1356
+ total: result.total,
1357
+ items: result.items,
1358
+ }, null, 2),
1359
+ },
1360
+ ],
1361
+ };
1362
+ }
1363
+ case 'unroo_convert_to_backlog': {
1364
+ const { id, priority, notes } = args;
1365
+ const result = await unrooClient.updateFutureWork(id, {
1366
+ convert_to_backlog: true,
1367
+ priority,
1368
+ notes,
1369
+ });
1370
+ return {
1371
+ content: [
1372
+ {
1373
+ type: 'text',
1374
+ text: JSON.stringify({
1375
+ success: true,
1376
+ message: `Parking lot item ${id} converted to backlog`,
1377
+ item: result.item,
1378
+ }, null, 2),
1379
+ },
1380
+ ],
1381
+ };
1382
+ }
955
1383
  default:
956
1384
  throw new Error(`Unknown tool: ${name}`);
957
1385
  }
@@ -1065,6 +1493,15 @@ async function main() {
1065
1493
  const transport = new StdioServerTransport();
1066
1494
  await server.connect(transport);
1067
1495
  console.error(`FCP MCP Server v${MCP_SERVER_VERSION} running on stdio`);
1496
+ console.error(` FCP API: ${FCP_API_URL}`);
1497
+ if (USE_FCP_UNROO_PROXY) {
1498
+ console.error(' Unroo: via FCP proxy (unified key mode)');
1499
+ }
1500
+ else {
1501
+ console.error(` Unroo: direct (${UNROO_API_URL})`);
1502
+ }
1503
+ // Auto-detect project from git remote (non-blocking)
1504
+ initializeProjectDetection();
1068
1505
  // Check for updates (non-blocking)
1069
1506
  checkForUpdates();
1070
1507
  // Handle graceful shutdown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fruition/fcp-mcp-server",
3
- "version": "1.3.0",
3
+ "version": "1.5.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",