@fruition/fcp-mcp-server 1.1.0 → 1.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # FCP MCP Server
2
2
 
3
- MCP (Model Context Protocol) server that gives Claude Code direct access to the FCP Launch Coordination System.
3
+ MCP (Model Context Protocol) server that gives Claude Code direct access to the FCP Launch Coordination System. Also includes automatic time tracking integration with Unroo.
4
4
 
5
5
  ## Features
6
6
 
@@ -149,3 +149,76 @@ To get an API token for the MCP server:
149
149
  ### Tools not appearing
150
150
  - Restart Claude Code after updating settings
151
151
  - Check Claude Code logs for MCP server errors
152
+
153
+ ---
154
+
155
+ ## Time Tracking with Unroo
156
+
157
+ This package also includes `unroo-heartbeat`, a script that automatically tracks your Claude Code development time in Unroo.
158
+
159
+ ### Quick Setup
160
+
161
+ After installing the package globally (`npm install -g @fruition/fcp-mcp-server`), configure time tracking:
162
+
163
+ #### 1. Set your Unroo API key
164
+
165
+ Add to `~/.bashrc` or `~/.zshrc`:
166
+
167
+ ```bash
168
+ export UNROO_API_KEY="mcp_XXXXXX_your_key_here"
169
+ ```
170
+
171
+ Get your API key from Unroo: Settings → API Keys → Create with "Time Tracking" enabled.
172
+
173
+ #### 2. Configure Claude Code hooks
174
+
175
+ Add to `~/.claude/settings.json`:
176
+
177
+ ```json
178
+ {
179
+ "hooks": {
180
+ "PostToolUse": [
181
+ {
182
+ "matcher": "*",
183
+ "hooks": [
184
+ {
185
+ "type": "command",
186
+ "command": "unroo-heartbeat"
187
+ }
188
+ ]
189
+ }
190
+ ],
191
+ "Stop": [
192
+ {
193
+ "hooks": [
194
+ {
195
+ "type": "command",
196
+ "command": "unroo-heartbeat session_end"
197
+ }
198
+ ]
199
+ }
200
+ ]
201
+ }
202
+ }
203
+ ```
204
+
205
+ #### 3. Restart Claude Code
206
+
207
+ Time tracking is now active. Sessions are automatically mapped to the correct Unroo project based on:
208
+ 1. `.unroo` file in project root (if present)
209
+ 2. Saved repo→project mappings (learned from previous sessions)
210
+ 3. Smart matching by repo name to Jira project key
211
+
212
+ ### Project Configuration (Optional)
213
+
214
+ For repos that don't auto-match, create a `.unroo` file:
215
+
216
+ ```bash
217
+ echo "project_slug=YOUR_JIRA_KEY" > .unroo
218
+ ```
219
+
220
+ The system learns from this and saves the mapping permanently, so you only need to do this once per repo.
221
+
222
+ ### Viewing Time
223
+
224
+ Log in to [Unroo](https://chat.frugpt.com) and check My Day or Timesheet to see your tracked sessions.
@@ -0,0 +1,90 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # UNROO CLAUDE CODE HEARTBEAT SCRIPT
4
+ # ============================================================================
5
+ # Sends heartbeat signals to UnRoo time tracker API
6
+ # Called by Claude Code hooks on tool use events
7
+ #
8
+ # Usage:
9
+ # unroo-heartbeat.sh [event_type]
10
+ #
11
+ # Event types:
12
+ # tool_end (default) - After tool completes
13
+ # session_end - When Claude Code session ends
14
+ # heartbeat - Periodic heartbeat from watcher
15
+ #
16
+ # Environment variables:
17
+ # UNROO_API_KEY - Required: Your UnRoo API key with time tracking enabled
18
+ # UNROO_API_ENDPOINT - Optional: API base URL (default: https://chat.frugpt.com)
19
+ # TOOL_NAME - Optional: Name of the tool that was used
20
+ # TOOL_DESCRIPTION - Optional: Description of what the tool did
21
+ #
22
+ # Project Configuration:
23
+ # Create a .unroo file in your project root with:
24
+ # project_slug=YOUR_PROJECT_SLUG
25
+ # This maps time to the correct Unroo project (optional - auto-matching works for most repos).
26
+ #
27
+ # Installation:
28
+ # npm install -g @fruition/fcp-mcp-server
29
+ # Then set UNROO_API_KEY in your shell profile and configure Claude Code hooks.
30
+ # ============================================================================
31
+
32
+ # Configuration
33
+ EVENT_TYPE="${1:-tool_end}"
34
+ API_ENDPOINT="${UNROO_API_ENDPOINT:-https://chat.frugpt.com}"
35
+ API_URL="${API_ENDPOINT}/api/unroo/claude-tracker/heartbeat"
36
+
37
+ # Check for API key
38
+ if [ -z "$UNROO_API_KEY" ]; then
39
+ # Silently exit if no API key - don't block Claude Code
40
+ exit 0
41
+ fi
42
+
43
+ # Get git remote URL (if in a git repo)
44
+ GIT_REMOTE=""
45
+ if git rev-parse --git-dir > /dev/null 2>&1; then
46
+ GIT_REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
47
+ fi
48
+
49
+ # Get repo name from current directory
50
+ REPO_NAME=$(basename "$(pwd)")
51
+
52
+ # Check for project-specific config (.unroo file)
53
+ PROJECT_SLUG=""
54
+ if [ -f ".unroo" ]; then
55
+ PROJECT_SLUG=$(grep -E "^project_slug=" .unroo 2>/dev/null | cut -d= -f2)
56
+ fi
57
+
58
+ # Get machine ID (for multi-machine tracking)
59
+ MACHINE_ID=""
60
+ if [ -f /etc/machine-id ]; then
61
+ MACHINE_ID=$(cat /etc/machine-id | head -c 8)
62
+ elif command -v hostid &> /dev/null; then
63
+ MACHINE_ID=$(hostid)
64
+ fi
65
+
66
+ # Build JSON payload
67
+ JSON_PAYLOAD=$(cat <<EOF
68
+ {
69
+ "api_key": "${UNROO_API_KEY}",
70
+ "event_type": "${EVENT_TYPE}",
71
+ "source": "claude-code",
72
+ "git_remote_url": "${GIT_REMOTE}",
73
+ "repo_name": "${REPO_NAME}",
74
+ "machine_id": "${MACHINE_ID}",
75
+ "tool_name": "${TOOL_NAME:-}",
76
+ "tool_description": "${TOOL_DESCRIPTION:-}",
77
+ "project_slug": "${PROJECT_SLUG:-}"
78
+ }
79
+ EOF
80
+ )
81
+
82
+ # Send heartbeat in background to not block Claude Code
83
+ # Redirect all output to /dev/null to avoid any terminal output
84
+ curl -s -X POST "$API_URL" \
85
+ -H "Content-Type: application/json" \
86
+ -d "$JSON_PAYLOAD" \
87
+ > /dev/null 2>&1 &
88
+
89
+ # Exit immediately - don't wait for curl
90
+ exit 0
package/dist/index.js CHANGED
@@ -10,10 +10,33 @@
10
10
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12
12
  import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
13
+ import { readFileSync } from 'fs';
14
+ import { fileURLToPath } from 'url';
15
+ import { dirname, join } from 'path';
16
+ // Get version from package.json
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+ const packageJsonPath = join(__dirname, '..', 'package.json');
20
+ let MCP_SERVER_VERSION = '1.0.0'; // fallback
21
+ try {
22
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
23
+ MCP_SERVER_VERSION = packageJson.version;
24
+ }
25
+ catch {
26
+ // Try one level up (for dist folder)
27
+ try {
28
+ const altPath = join(__dirname, '..', '..', 'package.json');
29
+ const packageJson = JSON.parse(readFileSync(altPath, 'utf-8'));
30
+ MCP_SERVER_VERSION = packageJson.version;
31
+ }
32
+ catch {
33
+ console.error('[MCP Server] Warning: Could not read package.json version');
34
+ }
35
+ }
13
36
  // Configuration
14
37
  const FCP_API_URL = process.env.FCP_API_URL || 'https://fcp.fru.io';
15
38
  const FCP_API_TOKEN = process.env.FCP_API_TOKEN || '';
16
- const UNROO_API_URL = process.env.UNROO_API_URL || 'https://frugpt.fruition.net';
39
+ const UNROO_API_URL = process.env.UNROO_API_URL || 'https://chat.frugpt.com';
17
40
  const UNROO_API_KEY = process.env.UNROO_API_KEY || '';
18
41
  // API Client
19
42
  class FCPClient {
@@ -283,7 +306,7 @@ class SessionTracker {
283
306
  // Create server
284
307
  const server = new Server({
285
308
  name: 'fcp-mcp-server',
286
- version: '1.0.0',
309
+ version: MCP_SERVER_VERSION,
287
310
  }, {
288
311
  capabilities: {
289
312
  tools: {},
@@ -990,11 +1013,60 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
990
1013
  }
991
1014
  throw new Error(`Unknown resource: ${uri}`);
992
1015
  });
1016
+ // Version comparison helper
1017
+ function compareVersions(v1, v2) {
1018
+ const parts1 = v1.split('.').map(Number);
1019
+ const parts2 = v2.split('.').map(Number);
1020
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1021
+ const p1 = parts1[i] || 0;
1022
+ const p2 = parts2[i] || 0;
1023
+ if (p1 > p2)
1024
+ return 1;
1025
+ if (p1 < p2)
1026
+ return -1;
1027
+ }
1028
+ return 0;
1029
+ }
1030
+ // Check for updates on startup
1031
+ async function checkForUpdates() {
1032
+ if (!FCP_API_URL)
1033
+ return;
1034
+ try {
1035
+ const response = await fetch(`${FCP_API_URL}/api/mcp/version`, {
1036
+ headers: FCP_API_TOKEN ? { 'X-API-Key': FCP_API_TOKEN } : {},
1037
+ });
1038
+ if (!response.ok) {
1039
+ // Silently ignore - version endpoint may not exist on older FCP versions
1040
+ return;
1041
+ }
1042
+ const data = await response.json();
1043
+ const latestVersion = data.version;
1044
+ const minVersion = data.minVersion;
1045
+ if (compareVersions(MCP_SERVER_VERSION, minVersion) < 0) {
1046
+ console.error(`\n⚠️ [MCP Server] INCOMPATIBLE VERSION`);
1047
+ console.error(` Your version: ${MCP_SERVER_VERSION}`);
1048
+ console.error(` Minimum required: ${minVersion}`);
1049
+ console.error(` Please update: ${data.updateUrl}\n`);
1050
+ }
1051
+ else if (compareVersions(MCP_SERVER_VERSION, latestVersion) < 0) {
1052
+ console.error(`\n📦 [MCP Server] Update available: ${MCP_SERVER_VERSION} → ${latestVersion}`);
1053
+ console.error(` Update: ${data.updateUrl}\n`);
1054
+ }
1055
+ else {
1056
+ console.error(`[MCP Server] Version ${MCP_SERVER_VERSION} (up to date)`);
1057
+ }
1058
+ }
1059
+ catch {
1060
+ // Silently ignore network errors - don't block startup
1061
+ }
1062
+ }
993
1063
  // Start server
994
1064
  async function main() {
995
1065
  const transport = new StdioServerTransport();
996
1066
  await server.connect(transport);
997
- console.error('FCP MCP Server running on stdio');
1067
+ console.error(`FCP MCP Server v${MCP_SERVER_VERSION} running on stdio`);
1068
+ // Check for updates (non-blocking)
1069
+ checkForUpdates();
998
1070
  // Handle graceful shutdown
999
1071
  const shutdown = async () => {
1000
1072
  console.error('Shutting down MCP server...');
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@fruition/fcp-mcp-server",
3
- "version": "1.1.0",
4
- "description": "MCP Server for FCP Launch Coordination System - enables Claude Code to interact with FCP launches",
3
+ "version": "1.3.0",
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",
7
7
  "bin": {
8
- "fcp-mcp-server": "./dist/index.js"
8
+ "fcp-mcp-server": "./dist/index.js",
9
+ "unroo-heartbeat": "./bin/unroo-heartbeat.sh"
9
10
  },
10
11
  "files": [
11
- "dist"
12
+ "dist",
13
+ "bin"
12
14
  ],
13
15
  "scripts": {
14
16
  "build": "tsc",