@fruition/fcp-mcp-server 1.3.0 → 1.4.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 (2) hide show
  1. package/dist/index.js +134 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33,11 +33,88 @@ 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 || '';
39
40
  const UNROO_API_URL = process.env.UNROO_API_URL || 'https://chat.frugpt.com';
40
41
  const UNROO_API_KEY = process.env.UNROO_API_KEY || '';
42
+ let currentProject = null;
43
+ /**
44
+ * Detect the current git remote URL
45
+ */
46
+ function detectGitRemote() {
47
+ try {
48
+ const remote = execSync('git config --get remote.origin.url', {
49
+ encoding: 'utf-8',
50
+ timeout: 5000,
51
+ stdio: ['pipe', 'pipe', 'ignore'],
52
+ }).trim();
53
+ return remote || null;
54
+ }
55
+ catch {
56
+ // Not a git repo or no remote configured
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Resolve project from git remote URL via FCP API
62
+ */
63
+ async function resolveProjectFromRepo(repoUrl) {
64
+ if (!FCP_API_URL) {
65
+ return null;
66
+ }
67
+ try {
68
+ const url = `${FCP_API_URL}/api/mcp/resolve-project?repo_url=${encodeURIComponent(repoUrl)}`;
69
+ const headers = {
70
+ 'Content-Type': 'application/json',
71
+ };
72
+ if (FCP_API_TOKEN && FCP_API_TOKEN !== 'dev_bypass') {
73
+ headers['X-API-Key'] = FCP_API_TOKEN;
74
+ }
75
+ else if (FCP_API_TOKEN === 'dev_bypass') {
76
+ headers['X-Dev-Bypass'] = 'true';
77
+ }
78
+ const response = await fetch(url, { headers });
79
+ if (!response.ok) {
80
+ return null;
81
+ }
82
+ const data = await response.json();
83
+ if (data.found && data.project) {
84
+ return {
85
+ project_key: data.project.project_key,
86
+ website_id: data.project.website_id,
87
+ domain: data.project.domain,
88
+ account_name: data.project.account_name || 'Unknown',
89
+ github: data.project.github,
90
+ };
91
+ }
92
+ return null;
93
+ }
94
+ catch (error) {
95
+ console.error('[MCP Server] Error resolving project:', error);
96
+ return null;
97
+ }
98
+ }
99
+ /**
100
+ * Initialize project detection on startup
101
+ */
102
+ async function initializeProjectDetection() {
103
+ const remoteUrl = detectGitRemote();
104
+ if (!remoteUrl) {
105
+ console.error('[MCP Server] No git remote detected - project will be tracked as general');
106
+ return;
107
+ }
108
+ console.error(`[MCP Server] Detected git remote: ${remoteUrl}`);
109
+ const project = await resolveProjectFromRepo(remoteUrl);
110
+ if (project) {
111
+ currentProject = project;
112
+ console.error(`[MCP Server] Resolved project: ${project.domain} (${project.project_key})`);
113
+ }
114
+ else {
115
+ console.error('[MCP Server] Could not resolve project from repo - check FCP website github config');
116
+ }
117
+ }
41
118
  // API Client
42
119
  class FCPClient {
43
120
  baseUrl;
@@ -180,6 +257,19 @@ class UnrooClient {
180
257
  body: JSON.stringify({ action: 'heartbeat', ...input }),
181
258
  });
182
259
  }
260
+ async logActivity(taskId, input) {
261
+ return this.fetch(`/api/external/fcp/tasks/${encodeURIComponent(taskId)}/activity`, {
262
+ method: 'POST',
263
+ body: JSON.stringify({
264
+ activity_type: input.activity_type,
265
+ source: input.source || 'claude_code',
266
+ field_changed: input.field_changed,
267
+ old_value: input.old_value,
268
+ new_value: input.new_value,
269
+ metadata: input.metadata,
270
+ }),
271
+ });
272
+ }
183
273
  }
184
274
  // ============================================================================
185
275
  // Auto Session Tracking
@@ -234,18 +324,30 @@ class SessionTracker {
234
324
  return;
235
325
  }
236
326
  try {
237
- await this.unrooClient.startSession({
327
+ // Use auto-detected project if available
328
+ const sessionInput = {
238
329
  task_id: this.currentTaskId || undefined,
239
330
  source: 'claude-code-mcp',
240
331
  machine_id: process.env.HOSTNAME || 'unknown',
241
- });
332
+ };
333
+ // Add auto-detected project info
334
+ if (currentProject) {
335
+ sessionInput.project_key = currentProject.project_key;
336
+ sessionInput.repo_name = `${currentProject.github.owner}/${currentProject.github.repo}`;
337
+ }
338
+ await this.unrooClient.startSession(sessionInput);
242
339
  this.sessionActive = true;
243
340
  this.lastHeartbeat = new Date();
244
341
  // Set up periodic heartbeat (every 5 minutes)
245
342
  this.heartbeatInterval = setInterval(() => {
246
343
  this.sendHeartbeat().catch(() => { });
247
344
  }, 5 * 60 * 1000);
248
- console.error('[SessionTracker] Session started');
345
+ if (currentProject) {
346
+ console.error(`[SessionTracker] Session started for project: ${currentProject.domain}`);
347
+ }
348
+ else {
349
+ console.error('[SessionTracker] Session started (no project detected)');
350
+ }
249
351
  }
250
352
  catch (error) {
251
353
  console.error('[SessionTracker] Failed to start session:', error);
@@ -272,7 +374,7 @@ class SessionTracker {
272
374
  }
273
375
  }
274
376
  /**
275
- * End the current session
377
+ * End the current session and log activity summary to task
276
378
  */
277
379
  async endSession() {
278
380
  if (!this.sessionActive || !UNROO_API_KEY) {
@@ -283,9 +385,34 @@ class SessionTracker {
283
385
  this.heartbeatInterval = null;
284
386
  }
285
387
  try {
286
- await this.unrooClient.endSession();
388
+ // End the session first
389
+ const result = await this.unrooClient.endSession();
287
390
  this.sessionActive = false;
288
391
  console.error('[SessionTracker] Session ended');
392
+ // Log activity summary to the task if we have a current task
393
+ if (this.currentTaskId && result.session) {
394
+ try {
395
+ const session = result.session;
396
+ const uniqueTools = [...new Set(this.activities.map(a => a.tool))];
397
+ await this.unrooClient.logActivity(this.currentTaskId, {
398
+ activity_type: 'claude_code_session',
399
+ source: 'claude_code',
400
+ metadata: {
401
+ session_id: session.id,
402
+ duration_seconds: session.duration_minutes ? session.duration_minutes * 60 : 0,
403
+ total_tool_calls: session.total_tool_calls || this.toolCallCount,
404
+ tools_used: uniqueTools.slice(0, 20), // Limit to 20 tools
405
+ started_at: session.started_at,
406
+ ended_at: session.ended_at || new Date().toISOString(),
407
+ },
408
+ });
409
+ console.error(`[SessionTracker] Logged session activity to task ${this.currentTaskId}`);
410
+ }
411
+ catch (activityError) {
412
+ console.error('[SessionTracker] Failed to log activity to task:', activityError);
413
+ // Don't throw - session end was successful
414
+ }
415
+ }
289
416
  }
290
417
  catch (error) {
291
418
  console.error('[SessionTracker] Failed to end session:', error);
@@ -1065,6 +1192,8 @@ async function main() {
1065
1192
  const transport = new StdioServerTransport();
1066
1193
  await server.connect(transport);
1067
1194
  console.error(`FCP MCP Server v${MCP_SERVER_VERSION} running on stdio`);
1195
+ // Auto-detect project from git remote (non-blocking)
1196
+ initializeProjectDetection();
1068
1197
  // Check for updates (non-blocking)
1069
1198
  checkForUpdates();
1070
1199
  // 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.4.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",