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