fraim-framework 2.0.90 ā 2.0.91
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/bin/fraim-mcp.js
CHANGED
|
@@ -50,11 +50,11 @@ function extractLeadParagraph(content) {
|
|
|
50
50
|
* These stubs are committed to the user's repo for discoverability.
|
|
51
51
|
*/
|
|
52
52
|
function generateWorkflowStub(workflowName, workflowPath, intent, principles) {
|
|
53
|
-
return `${STUB_MARKER}
|
|
54
|
-
# FRAIM Workflow: ${workflowName}
|
|
55
|
-
|
|
56
|
-
> [!IMPORTANT]
|
|
57
|
-
> This is a **FRAIM-managed workflow stub**.
|
|
53
|
+
return `${STUB_MARKER}
|
|
54
|
+
# FRAIM Workflow: ${workflowName}
|
|
55
|
+
|
|
56
|
+
> [!IMPORTANT]
|
|
57
|
+
> This is a **FRAIM-managed workflow stub**.
|
|
58
58
|
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
59
59
|
> \`@fraim get_fraim_workflow("${workflowName}")\`
|
|
60
60
|
>
|
|
@@ -81,19 +81,19 @@ function parseRegistryWorkflow(content) {
|
|
|
81
81
|
* Coaching stubs are discoverability artifacts and should be resolved with get_fraim_file.
|
|
82
82
|
*/
|
|
83
83
|
function generateJobStub(jobName, _jobPath, intent, outcome, steps) {
|
|
84
|
-
return `${STUB_MARKER}
|
|
85
|
-
# FRAIM Job: ${jobName}
|
|
86
|
-
|
|
87
|
-
## Intent
|
|
88
|
-
${intent}
|
|
89
|
-
|
|
90
|
-
## Outcome
|
|
91
|
-
${outcome}
|
|
92
|
-
|
|
93
|
-
## Steps
|
|
94
|
-
${steps}
|
|
95
|
-
|
|
96
|
-
---
|
|
84
|
+
return `${STUB_MARKER}
|
|
85
|
+
# FRAIM Job: ${jobName}
|
|
86
|
+
|
|
87
|
+
## Intent
|
|
88
|
+
${intent}
|
|
89
|
+
|
|
90
|
+
## Outcome
|
|
91
|
+
${outcome}
|
|
92
|
+
|
|
93
|
+
## Steps
|
|
94
|
+
${steps}
|
|
95
|
+
|
|
96
|
+
---
|
|
97
97
|
|
|
98
98
|
> [!IMPORTANT]
|
|
99
99
|
> **For AI Agents:** Do NOT attempt to execute this job based on the Intent/Outcome above.
|
|
@@ -110,41 +110,41 @@ ${steps}
|
|
|
110
110
|
* Generates a lightweight markdown stub for a skill.
|
|
111
111
|
*/
|
|
112
112
|
function generateSkillStub(skillName, skillPath, skillInput, skillOutput) {
|
|
113
|
-
return `${STUB_MARKER}
|
|
114
|
-
# FRAIM Skill: ${skillName}
|
|
115
|
-
|
|
116
|
-
## Skill Input
|
|
117
|
-
${skillInput}
|
|
118
|
-
|
|
119
|
-
## Skill Output
|
|
120
|
-
${skillOutput}
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
> [!IMPORTANT]
|
|
125
|
-
> **For AI Agents:** This is a discoverability stub for the skill.
|
|
126
|
-
> All execution details must be fetched from MCP before use.
|
|
127
|
-
> To retrieve the complete skill instructions, call:
|
|
128
|
-
> \`get_fraim_file({ path: "skills/${skillPath}" })\`
|
|
113
|
+
return `${STUB_MARKER}
|
|
114
|
+
# FRAIM Skill: ${skillName}
|
|
115
|
+
|
|
116
|
+
## Skill Input
|
|
117
|
+
${skillInput}
|
|
118
|
+
|
|
119
|
+
## Skill Output
|
|
120
|
+
${skillOutput}
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
> [!IMPORTANT]
|
|
125
|
+
> **For AI Agents:** This is a discoverability stub for the skill.
|
|
126
|
+
> All execution details must be fetched from MCP before use.
|
|
127
|
+
> To retrieve the complete skill instructions, call:
|
|
128
|
+
> \`get_fraim_file({ path: "skills/${skillPath}" })\`
|
|
129
129
|
`;
|
|
130
130
|
}
|
|
131
131
|
/**
|
|
132
132
|
* Generates a lightweight markdown stub for a rule.
|
|
133
133
|
*/
|
|
134
134
|
function generateRuleStub(ruleName, rulePath, intent) {
|
|
135
|
-
return `${STUB_MARKER}
|
|
136
|
-
# FRAIM Rule: ${ruleName}
|
|
137
|
-
|
|
138
|
-
## Intent
|
|
139
|
-
${intent}
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
> [!IMPORTANT]
|
|
144
|
-
> **For AI Agents:** This is a discoverability stub for the rule.
|
|
145
|
-
> All rule details must be fetched from MCP before use.
|
|
146
|
-
> To retrieve the complete rule instructions, call:
|
|
147
|
-
> \`get_fraim_file({ path: "rules/${rulePath}" })\`
|
|
135
|
+
return `${STUB_MARKER}
|
|
136
|
+
# FRAIM Rule: ${ruleName}
|
|
137
|
+
|
|
138
|
+
## Intent
|
|
139
|
+
${intent}
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
> [!IMPORTANT]
|
|
144
|
+
> **For AI Agents:** This is a discoverability stub for the rule.
|
|
145
|
+
> All rule details must be fetched from MCP before use.
|
|
146
|
+
> To retrieve the complete rule instructions, call:
|
|
147
|
+
> \`get_fraim_file({ path: "rules/${rulePath}" })\`
|
|
148
148
|
`;
|
|
149
149
|
}
|
|
150
150
|
/**
|
|
@@ -29,6 +29,7 @@ const provider_utils_1 = require("../core/utils/provider-utils");
|
|
|
29
29
|
const object_utils_1 = require("../core/utils/object-utils");
|
|
30
30
|
const local_registry_resolver_1 = require("../core/utils/local-registry-resolver");
|
|
31
31
|
const ai_mentor_1 = require("../core/ai-mentor");
|
|
32
|
+
const usage_collector_js_1 = require("./usage-collector.js");
|
|
32
33
|
/**
|
|
33
34
|
* Handle template substitution logic separately for better testability
|
|
34
35
|
*/
|
|
@@ -264,6 +265,9 @@ class FraimLocalMCPServer {
|
|
|
264
265
|
this.log(`š API key: ${this.apiKey.substring(0, 10)}...`);
|
|
265
266
|
this.log(`Local MCP version: ${this.localVersion}`);
|
|
266
267
|
this.log(`š DEBUG BUILD: Machine detection v2 active`);
|
|
268
|
+
// Initialize usage collector
|
|
269
|
+
this.usageCollector = new usage_collector_js_1.UsageCollector();
|
|
270
|
+
this.log('š Usage analytics collector initialized');
|
|
267
271
|
}
|
|
268
272
|
/**
|
|
269
273
|
* Load API key from environment variable or user config file
|
|
@@ -1402,9 +1406,9 @@ class FraimLocalMCPServer {
|
|
|
1402
1406
|
// Force ALL tools/call requests to return raw definitions so the proxy
|
|
1403
1407
|
// can resolve templates and replace includes locally
|
|
1404
1408
|
const toolName = request.params?.name;
|
|
1409
|
+
const args = request.params?.arguments || {};
|
|
1405
1410
|
let injectedRequest = request;
|
|
1406
1411
|
if (request.method === 'tools/call' && typeof toolName === 'string') {
|
|
1407
|
-
const args = request.params.arguments || {};
|
|
1408
1412
|
// š SMART DISPATCHER: Intercept mentoring and job/workflow tools for local overrides
|
|
1409
1413
|
if (toolName === 'seekMentoring') {
|
|
1410
1414
|
try {
|
|
@@ -1482,6 +1486,11 @@ class FraimLocalMCPServer {
|
|
|
1482
1486
|
}
|
|
1483
1487
|
const response = await this._doProxyToRemote(injectedRequest, requestId);
|
|
1484
1488
|
const processedResponse = await this.finalizeToolResponse(injectedRequest, response, requestSessionId, requestId);
|
|
1489
|
+
// Single point for usage tracking - log all tool calls
|
|
1490
|
+
if (injectedRequest.method === 'tools/call' && requestSessionId && toolName) {
|
|
1491
|
+
const success = !processedResponse.error;
|
|
1492
|
+
this.usageCollector.collectMCPCall(toolName, args, requestSessionId, success);
|
|
1493
|
+
}
|
|
1485
1494
|
this.log(`š¤ ${injectedRequest.method} ā ${processedResponse.error ? 'ERROR' : 'OK'}`);
|
|
1486
1495
|
return processedResponse;
|
|
1487
1496
|
}
|
|
@@ -1574,6 +1583,17 @@ class FraimLocalMCPServer {
|
|
|
1574
1583
|
start() {
|
|
1575
1584
|
let buffer = '';
|
|
1576
1585
|
process.stdin.setEncoding('utf8');
|
|
1586
|
+
// Set up periodic usage data upload
|
|
1587
|
+
const uploadInterval = setInterval(() => {
|
|
1588
|
+
this.uploadUsageData().catch(error => {
|
|
1589
|
+
this.log(`ā ļø Failed to upload usage data: ${error.message}`);
|
|
1590
|
+
});
|
|
1591
|
+
}, 60000); // Upload every minute
|
|
1592
|
+
// Clean up interval on shutdown
|
|
1593
|
+
const cleanup = () => {
|
|
1594
|
+
clearInterval(uploadInterval);
|
|
1595
|
+
this.usageCollector.shutdown();
|
|
1596
|
+
};
|
|
1577
1597
|
process.stdin.on('data', async (chunk) => {
|
|
1578
1598
|
buffer += chunk;
|
|
1579
1599
|
// Process complete JSON-RPC messages (newline-delimited)
|
|
@@ -1629,21 +1649,52 @@ class FraimLocalMCPServer {
|
|
|
1629
1649
|
});
|
|
1630
1650
|
process.stdin.on('end', () => {
|
|
1631
1651
|
this.log('š Stdin closed, shutting down...');
|
|
1652
|
+
cleanup();
|
|
1632
1653
|
this.emitFallbackSummary('stdin_end');
|
|
1633
1654
|
process.exit(0);
|
|
1634
1655
|
});
|
|
1635
1656
|
process.on('SIGTERM', () => {
|
|
1636
1657
|
this.log('š SIGTERM received, shutting down...');
|
|
1658
|
+
cleanup();
|
|
1637
1659
|
this.emitFallbackSummary('sigterm');
|
|
1638
1660
|
process.exit(0);
|
|
1639
1661
|
});
|
|
1640
1662
|
process.on('SIGINT', () => {
|
|
1641
1663
|
this.log('š SIGINT received, shutting down...');
|
|
1664
|
+
cleanup();
|
|
1642
1665
|
this.emitFallbackSummary('sigint');
|
|
1643
1666
|
process.exit(0);
|
|
1644
1667
|
});
|
|
1645
1668
|
this.log('ā
FRAIM Local MCP Server ready');
|
|
1646
1669
|
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Upload collected usage data to the remote server
|
|
1672
|
+
*/
|
|
1673
|
+
async uploadUsageData() {
|
|
1674
|
+
const events = this.usageCollector.getEventsForUpload();
|
|
1675
|
+
if (events.length === 0) {
|
|
1676
|
+
return; // Nothing to upload
|
|
1677
|
+
}
|
|
1678
|
+
try {
|
|
1679
|
+
const response = await axios_1.default.post(`${this.remoteUrl}/api/analytics/events`, { events }, {
|
|
1680
|
+
headers: {
|
|
1681
|
+
'Content-Type': 'application/json',
|
|
1682
|
+
'x-api-key': this.apiKey
|
|
1683
|
+
},
|
|
1684
|
+
timeout: 10000
|
|
1685
|
+
});
|
|
1686
|
+
if (response.status === 200) {
|
|
1687
|
+
this.log(`š Uploaded ${events.length} usage events`);
|
|
1688
|
+
}
|
|
1689
|
+
else {
|
|
1690
|
+
this.log(`ā ļø Usage upload failed: ${response.status}`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
catch (error) {
|
|
1694
|
+
this.log(`ā Usage upload error: ${error.message}`);
|
|
1695
|
+
// Don't re-queue on failure - keep it simple
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1647
1698
|
}
|
|
1648
1699
|
exports.FraimLocalMCPServer = FraimLocalMCPServer;
|
|
1649
1700
|
FraimLocalMCPServer.AGENT_RESOLUTION_NOTICE = [
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UsageCollector = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Usage event collector for local MCP proxy
|
|
6
|
+
* Collects usage events and batches them for upload
|
|
7
|
+
*/
|
|
8
|
+
class UsageCollector {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.events = [];
|
|
11
|
+
this.apiKeyId = null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Set the API key ID for this session
|
|
15
|
+
*/
|
|
16
|
+
setApiKeyId(apiKeyId) {
|
|
17
|
+
this.apiKeyId = apiKeyId;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Collect MCP tool call event
|
|
21
|
+
*/
|
|
22
|
+
collectMCPCall(toolName, args, sessionId, success = true, duration) {
|
|
23
|
+
if (!this.apiKeyId) {
|
|
24
|
+
return; // Silently skip if no API key
|
|
25
|
+
}
|
|
26
|
+
const parsed = this.parseMCPCall(toolName, args);
|
|
27
|
+
if (!parsed)
|
|
28
|
+
return;
|
|
29
|
+
const event = {
|
|
30
|
+
type: parsed.type,
|
|
31
|
+
name: parsed.name,
|
|
32
|
+
apiKeyId: this.apiKeyId,
|
|
33
|
+
sessionId,
|
|
34
|
+
success
|
|
35
|
+
};
|
|
36
|
+
if (duration !== undefined) {
|
|
37
|
+
event.duration = duration;
|
|
38
|
+
}
|
|
39
|
+
this.events.push(event);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Collect usage event directly (for backward compatibility with tests)
|
|
43
|
+
*/
|
|
44
|
+
collectEvent(type, name, sessionId, success = true, duration) {
|
|
45
|
+
if (!this.apiKeyId) {
|
|
46
|
+
return; // Silently skip if no API key
|
|
47
|
+
}
|
|
48
|
+
const event = {
|
|
49
|
+
type,
|
|
50
|
+
name,
|
|
51
|
+
apiKeyId: this.apiKeyId,
|
|
52
|
+
sessionId,
|
|
53
|
+
success
|
|
54
|
+
};
|
|
55
|
+
if (duration !== undefined) {
|
|
56
|
+
event.duration = duration;
|
|
57
|
+
}
|
|
58
|
+
this.events.push(event);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse MCP tool call to extract usage analytics info
|
|
62
|
+
*/
|
|
63
|
+
parseMCPCall(toolName, args) {
|
|
64
|
+
switch (toolName) {
|
|
65
|
+
case 'get_fraim_job':
|
|
66
|
+
return { type: 'job', name: args.job || 'unknown' };
|
|
67
|
+
case 'get_fraim_file':
|
|
68
|
+
if (args.path) {
|
|
69
|
+
return UsageCollector.parseComponentName(args.path);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
case 'seekMentoring':
|
|
73
|
+
return { type: 'mentoring', name: args.workflowType || 'unknown' };
|
|
74
|
+
default:
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get collected events for upload and clear the queue
|
|
80
|
+
*/
|
|
81
|
+
getEventsForUpload() {
|
|
82
|
+
const eventsToUpload = [...this.events];
|
|
83
|
+
this.events = [];
|
|
84
|
+
return eventsToUpload;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get current event count
|
|
88
|
+
*/
|
|
89
|
+
getEventCount() {
|
|
90
|
+
return this.events.length;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Shutdown the collector
|
|
94
|
+
*/
|
|
95
|
+
shutdown() {
|
|
96
|
+
this.events = [];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Parse component name from file path
|
|
100
|
+
*/
|
|
101
|
+
static parseComponentName(path) {
|
|
102
|
+
if (path.includes('/jobs/')) {
|
|
103
|
+
const jobMatch = path.match(/\/jobs\/[^/]+\/[^/]+\/([^/]+)\.md$/);
|
|
104
|
+
if (jobMatch) {
|
|
105
|
+
return { type: 'job', name: jobMatch[1] };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (path.includes('/skills/')) {
|
|
109
|
+
const skillMatch = path.match(/\/skills\/[^/]+\/([^/]+)\.md$/);
|
|
110
|
+
if (skillMatch) {
|
|
111
|
+
return { type: 'skill', name: skillMatch[1] };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (path.includes('/rules/')) {
|
|
115
|
+
const ruleMatch = path.match(/\/rules\/[^/]+\/([^/]+)\.md$/);
|
|
116
|
+
if (ruleMatch) {
|
|
117
|
+
return { type: 'rule', name: ruleMatch[1] };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.UsageCollector = UsageCollector;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.91",
|
|
4
4
|
"description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.testMCPCommand = exports.runTestMCP = void 0;
|
|
40
|
-
const commander_1 = require("commander");
|
|
41
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
-
const fs_1 = __importDefault(require("fs"));
|
|
43
|
-
const path_1 = __importDefault(require("path"));
|
|
44
|
-
const ide_detector_1 = require("../setup/ide-detector");
|
|
45
|
-
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
46
|
-
const testIDEConfig = async (ide) => {
|
|
47
|
-
const result = {
|
|
48
|
-
ide: ide.name,
|
|
49
|
-
configExists: false,
|
|
50
|
-
configValid: false,
|
|
51
|
-
mcpServers: [],
|
|
52
|
-
errors: []
|
|
53
|
-
};
|
|
54
|
-
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
55
|
-
if (!fs_1.default.existsSync(configPath)) {
|
|
56
|
-
result.errors.push('Config file does not exist');
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
|
-
result.configExists = true;
|
|
60
|
-
try {
|
|
61
|
-
if (ide.configFormat === 'json') {
|
|
62
|
-
const configContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
63
|
-
const config = JSON.parse(configContent);
|
|
64
|
-
const servers = ide.configType === 'vscode' ? config.servers : config.mcpServers;
|
|
65
|
-
if (servers) {
|
|
66
|
-
result.configValid = true;
|
|
67
|
-
result.mcpServers = Object.keys(servers);
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
const expectedKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
71
|
-
result.errors.push(`No ${expectedKey} section found`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else if (ide.configFormat === 'toml') {
|
|
75
|
-
const configContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
76
|
-
// Simple TOML parsing for MCP servers
|
|
77
|
-
const serverMatches = configContent.match(/\[mcp_servers\.(\w+)\]/g);
|
|
78
|
-
if (serverMatches) {
|
|
79
|
-
result.configValid = true;
|
|
80
|
-
result.mcpServers = serverMatches.map(match => match.replace(/\[mcp_servers\.(\w+)\]/, '$1'));
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
result.errors.push('No mcp_servers sections found');
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
result.errors.push(`Failed to parse config: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
89
|
-
}
|
|
90
|
-
return result;
|
|
91
|
-
};
|
|
92
|
-
const checkGlobalSetup = () => {
|
|
93
|
-
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
94
|
-
return fs_1.default.existsSync(globalConfigPath);
|
|
95
|
-
};
|
|
96
|
-
const runTestMCP = async () => {
|
|
97
|
-
console.log(chalk_1.default.blue('š Testing MCP configuration...\n'));
|
|
98
|
-
// Check global setup
|
|
99
|
-
if (!checkGlobalSetup()) {
|
|
100
|
-
console.log(chalk_1.default.red('ā Global FRAIM setup not found.'));
|
|
101
|
-
console.log(chalk_1.default.yellow('Please run: fraim setup --key=<your-fraim-key>'));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
console.log(chalk_1.default.green('ā
Global FRAIM setup found'));
|
|
105
|
-
// Detect IDEs
|
|
106
|
-
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
107
|
-
if (detectedIDEs.length === 0) {
|
|
108
|
-
console.log(chalk_1.default.yellow('ā ļø No supported IDEs detected.'));
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
console.log(chalk_1.default.blue(`\nš Testing ${detectedIDEs.length} detected IDEs...\n`));
|
|
112
|
-
const results = await Promise.all(detectedIDEs.map(ide => testIDEConfig(ide)));
|
|
113
|
-
let totalConfigured = 0;
|
|
114
|
-
let totalWithFRAIM = 0;
|
|
115
|
-
for (const result of results) {
|
|
116
|
-
console.log(chalk_1.default.white(`š± ${result.ide}`));
|
|
117
|
-
if (!result.configExists) {
|
|
118
|
-
console.log(chalk_1.default.red(' ā No MCP config found'));
|
|
119
|
-
console.log(chalk_1.default.gray(` š” Run: fraim setup --ide=${result.ide.toLowerCase()}`));
|
|
120
|
-
}
|
|
121
|
-
else if (!result.configValid) {
|
|
122
|
-
console.log(chalk_1.default.yellow(' ā ļø Config exists but invalid'));
|
|
123
|
-
result.errors.forEach(error => {
|
|
124
|
-
console.log(chalk_1.default.red(` ā ${error}`));
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
totalConfigured++;
|
|
129
|
-
console.log(chalk_1.default.green(` ā
MCP config valid (${result.mcpServers.length} servers)`));
|
|
130
|
-
// Check for essential servers
|
|
131
|
-
const { BASE_MCP_SERVERS } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
|
|
132
|
-
const essentialServers = BASE_MCP_SERVERS.map(s => s.id); // fraim, git, playwright
|
|
133
|
-
const hasEssential = essentialServers.filter(server => result.mcpServers.includes(server));
|
|
134
|
-
if (hasEssential.includes('fraim')) {
|
|
135
|
-
totalWithFRAIM++;
|
|
136
|
-
console.log(chalk_1.default.green(' ā
FRAIM server configured'));
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
console.log(chalk_1.default.yellow(' ā ļø FRAIM server missing'));
|
|
140
|
-
}
|
|
141
|
-
if (hasEssential.length > 1) {
|
|
142
|
-
console.log(chalk_1.default.green(` ā
${hasEssential.length - 1} additional servers: ${hasEssential.filter(s => s !== 'fraim').join(', ')}`));
|
|
143
|
-
}
|
|
144
|
-
const missingEssential = essentialServers.filter(server => !result.mcpServers.includes(server));
|
|
145
|
-
if (missingEssential.length > 0) {
|
|
146
|
-
console.log(chalk_1.default.yellow(` ā ļø Missing servers: ${missingEssential.join(', ')}`));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
console.log(); // Empty line
|
|
150
|
-
}
|
|
151
|
-
// Summary
|
|
152
|
-
console.log(chalk_1.default.blue('š Summary:'));
|
|
153
|
-
console.log(chalk_1.default.green(` ā
${totalConfigured}/${detectedIDEs.length} IDEs have valid MCP configs`));
|
|
154
|
-
console.log(chalk_1.default.green(` ā
${totalWithFRAIM}/${detectedIDEs.length} IDEs have FRAIM configured`));
|
|
155
|
-
if (totalWithFRAIM === 0) {
|
|
156
|
-
console.log(chalk_1.default.red('\nā No IDEs have FRAIM configured!'));
|
|
157
|
-
console.log(chalk_1.default.yellow('š” Run: fraim setup --key=<your-fraim-key>'));
|
|
158
|
-
}
|
|
159
|
-
else if (totalWithFRAIM < detectedIDEs.length) {
|
|
160
|
-
console.log(chalk_1.default.yellow(`\nā ļø ${detectedIDEs.length - totalWithFRAIM} IDEs missing FRAIM configuration`));
|
|
161
|
-
console.log(chalk_1.default.yellow('š” Run: fraim setup to configure remaining IDEs'));
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
console.log(chalk_1.default.green('\nš All detected IDEs have FRAIM configured!'));
|
|
165
|
-
console.log(chalk_1.default.blue('š” Try running: fraim init-project in any project'));
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
exports.runTestMCP = runTestMCP;
|
|
169
|
-
exports.testMCPCommand = new commander_1.Command('test-mcp')
|
|
170
|
-
.description('Test MCP server configurations for all detected IDEs')
|
|
171
|
-
.action(exports.runTestMCP);
|