fraim-framework 2.0.26 → 2.0.30

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 (104) hide show
  1. package/.github/workflows/deploy-fraim.yml +1 -1
  2. package/dist/registry/scripts/build-scripts-generator.js +205 -0
  3. package/dist/registry/scripts/cleanup-branch.js +258 -0
  4. package/dist/registry/scripts/evaluate-code-quality.js +66 -0
  5. package/dist/registry/scripts/exec-with-timeout.js +142 -0
  6. package/dist/registry/scripts/fraim-config.js +61 -0
  7. package/dist/registry/scripts/generate-engagement-emails.js +630 -0
  8. package/dist/registry/scripts/generic-issues-api.js +100 -0
  9. package/dist/registry/scripts/newsletter-helpers.js +731 -0
  10. package/dist/registry/scripts/openapi-generator.js +664 -0
  11. package/dist/registry/scripts/performance/profile-server.js +390 -0
  12. package/dist/registry/scripts/run-thank-you-workflow.js +92 -0
  13. package/dist/registry/scripts/send-newsletter-simple.js +85 -0
  14. package/dist/registry/scripts/send-thank-you-emails.js +54 -0
  15. package/dist/registry/scripts/validate-openapi-limits.js +311 -0
  16. package/dist/registry/scripts/validate-test-coverage.js +262 -0
  17. package/dist/registry/scripts/verify-test-coverage.js +66 -0
  18. package/dist/src/cli/commands/init.js +14 -12
  19. package/dist/src/cli/commands/sync.js +19 -2
  20. package/dist/src/cli/fraim.js +24 -22
  21. package/dist/src/cli/setup/first-run.js +13 -6
  22. package/dist/src/fraim/config-loader.js +0 -8
  23. package/dist/src/fraim/db-service.js +26 -15
  24. package/dist/src/fraim/issues.js +67 -0
  25. package/dist/src/fraim/setup-wizard.js +1 -69
  26. package/dist/src/fraim/types.js +0 -11
  27. package/dist/src/fraim-mcp-server.js +272 -18
  28. package/dist/src/utils/git-utils.js +1 -1
  29. package/dist/src/utils/version-utils.js +32 -0
  30. package/dist/tests/debug-tools.js +79 -0
  31. package/dist/tests/esm-compat.js +11 -0
  32. package/dist/tests/test-chalk-esm-issue.js +159 -0
  33. package/dist/tests/test-chalk-real-world.js +265 -0
  34. package/dist/tests/test-chalk-regression.js +327 -0
  35. package/dist/tests/test-chalk-resolution-issue.js +304 -0
  36. package/dist/tests/test-cli.js +0 -2
  37. package/dist/tests/test-fraim-install-chalk-issue.js +254 -0
  38. package/dist/tests/test-fraim-issues.js +59 -0
  39. package/dist/tests/test-genericization.js +1 -3
  40. package/dist/tests/test-mcp-connection.js +166 -0
  41. package/dist/tests/test-mcp-issue-integration.js +144 -0
  42. package/dist/tests/test-mcp-lifecycle-methods.js +312 -0
  43. package/dist/tests/test-node-compatibility.js +71 -0
  44. package/dist/tests/test-npm-install.js +66 -0
  45. package/dist/tests/test-npm-resolution-diagnostic.js +140 -0
  46. package/dist/tests/test-session-rehydration.js +145 -0
  47. package/dist/tests/test-standalone.js +2 -8
  48. package/dist/tests/test-sync-version-update.js +93 -0
  49. package/dist/tests/test-telemetry.js +190 -0
  50. package/package.json +10 -8
  51. package/registry/agent-guardrails.md +62 -54
  52. package/registry/rules/agent-success-criteria.md +52 -0
  53. package/registry/rules/agent-testing-guidelines.md +502 -502
  54. package/registry/rules/communication.md +121 -121
  55. package/registry/rules/continuous-learning.md +54 -54
  56. package/registry/rules/ephemeral-execution.md +10 -5
  57. package/registry/rules/hitl-ppe-record-analysis.md +302 -302
  58. package/registry/rules/local-development.md +251 -251
  59. package/registry/rules/software-development-lifecycle.md +104 -104
  60. package/registry/rules/successful-debugging-patterns.md +482 -478
  61. package/registry/rules/telemetry.md +67 -0
  62. package/registry/scripts/build-scripts-generator.ts +216 -215
  63. package/registry/scripts/cleanup-branch.ts +303 -284
  64. package/registry/scripts/code-quality-check.sh +559 -559
  65. package/registry/scripts/detect-tautological-tests.sh +38 -38
  66. package/registry/scripts/evaluate-code-quality.ts +1 -1
  67. package/registry/scripts/generate-engagement-emails.ts +744 -744
  68. package/registry/scripts/generic-issues-api.ts +110 -150
  69. package/registry/scripts/newsletter-helpers.ts +874 -874
  70. package/registry/scripts/openapi-generator.ts +695 -693
  71. package/registry/scripts/performance/profile-server.ts +5 -3
  72. package/registry/scripts/prep-issue.sh +468 -455
  73. package/registry/scripts/validate-openapi-limits.ts +366 -365
  74. package/registry/scripts/validate-test-coverage.ts +280 -280
  75. package/registry/scripts/verify-pr-comments.sh +70 -70
  76. package/registry/scripts/verify-test-coverage.ts +1 -1
  77. package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -53
  78. package/registry/templates/evidence/Implementation-BugEvidence.md +85 -85
  79. package/registry/templates/evidence/Implementation-FeatureEvidence.md +120 -120
  80. package/registry/templates/marketing/HBR-ARTICLE-TEMPLATE.md +66 -0
  81. package/registry/workflows/bootstrap/create-architecture.md +2 -2
  82. package/registry/workflows/bootstrap/evaluate-code-quality.md +3 -3
  83. package/registry/workflows/bootstrap/verify-test-coverage.md +2 -2
  84. package/registry/workflows/customer-development/insight-analysis.md +156 -156
  85. package/registry/workflows/customer-development/interview-preparation.md +421 -421
  86. package/registry/workflows/customer-development/strategic-brainstorming.md +146 -146
  87. package/registry/workflows/customer-development/thank-customers.md +193 -191
  88. package/registry/workflows/customer-development/weekly-newsletter.md +362 -352
  89. package/registry/workflows/improve-fraim/contribute.md +32 -0
  90. package/registry/workflows/improve-fraim/file-issue.md +32 -0
  91. package/registry/workflows/marketing/hbr-article.md +73 -0
  92. package/registry/workflows/performance/analyze-performance.md +63 -59
  93. package/registry/workflows/product-building/design.md +3 -2
  94. package/registry/workflows/product-building/implement.md +4 -3
  95. package/registry/workflows/product-building/prep-issue.md +28 -17
  96. package/registry/workflows/product-building/resolve.md +3 -2
  97. package/registry/workflows/product-building/retrospect.md +3 -2
  98. package/registry/workflows/product-building/spec.md +5 -4
  99. package/registry/workflows/product-building/test.md +3 -2
  100. package/registry/workflows/quality-assurance/iterative-improvement-cycle.md +562 -562
  101. package/registry/workflows/replicate/website-discovery-analysis.md +3 -3
  102. package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +632 -632
  103. package/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +669 -669
  104. package/tsconfig.json +2 -1
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_child_process_1 = require("node:child_process");
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const test_utils_1 = require("./test-utils");
9
+ const node_assert_1 = __importDefault(require("node:assert"));
10
+ const tree_kill_1 = __importDefault(require("tree-kill"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const db_service_js_1 = require("../src/fraim/db-service.js");
13
+ async function testMcpConnection() {
14
+ console.log(' 🚀 Testing MCP Connection Reliability...');
15
+ let fraimProcess;
16
+ let dbService;
17
+ const PORT = Math.floor(Math.random() * 1000) + 12000;
18
+ const BASE_URL = `http://localhost:${PORT}`;
19
+ const TEST_API_KEY = 'test-fraim-key-mcp-conn';
20
+ const TEST_ADMIN_KEY = 'test-admin-key-mcp-conn';
21
+ try {
22
+ // 0. Setup DB and Key
23
+ dbService = new db_service_js_1.FraimDbService();
24
+ await dbService.connect();
25
+ const db = dbService.db;
26
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY });
27
+ await db.collection('fraim_api_keys').insertOne({
28
+ key: TEST_API_KEY,
29
+ userId: 'test-user-mcp',
30
+ orgId: 'test-org',
31
+ isActive: true,
32
+ createdAt: new Date()
33
+ });
34
+ // 1. Start Server
35
+ console.log(` Starting server on port ${PORT}...`);
36
+ const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
37
+ const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
38
+ fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
39
+ env: {
40
+ ...process.env,
41
+ FRAIM_MCP_PORT: PORT.toString(),
42
+ FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
43
+ FRAIM_SKIP_INDEX_ON_START: 'true'
44
+ },
45
+ stdio: 'pipe',
46
+ shell: true
47
+ });
48
+ if (fraimProcess.stdout)
49
+ fraimProcess.stdout.pipe(process.stdout);
50
+ if (fraimProcess.stderr)
51
+ fraimProcess.stderr.pipe(process.stderr);
52
+ // Wait for start
53
+ let started = false;
54
+ for (let i = 0; i < 15; i++) {
55
+ try {
56
+ await axios_1.default.get(`${BASE_URL}/health`, { timeout: 500 });
57
+ started = true;
58
+ break;
59
+ }
60
+ catch (e) {
61
+ await new Promise(resolve => setTimeout(resolve, 1000));
62
+ }
63
+ }
64
+ if (!started)
65
+ throw new Error('Server failed to start');
66
+ console.log(' Server started!');
67
+ const authHeaders = { 'x-api-key': TEST_API_KEY };
68
+ // 2. Test notifications/initialized (Fix 1 verification)
69
+ console.log(' Testing notifications/initialized...');
70
+ try {
71
+ const res = await axios_1.default.post(`${BASE_URL}/mcp`, {
72
+ jsonrpc: '2.0',
73
+ method: 'notifications/initialized',
74
+ params: {}
75
+ }, { headers: authHeaders });
76
+ // Should be 200 OK now, not 500
77
+ node_assert_1.default.strictEqual(res.status, 200, 'Should return 200 OK for notifications/initialized');
78
+ node_assert_1.default.strictEqual(res.data.result, true, 'Should return result: true');
79
+ console.log(' ✅ notifications/initialized handled correctly.');
80
+ }
81
+ catch (error) {
82
+ console.error(' ❌ Notification failed:', error.message);
83
+ if (error.response) {
84
+ console.error(' Response:', JSON.stringify(error.response.data));
85
+ }
86
+ throw error;
87
+ }
88
+ // 3. Test DELETE request crash (Fix 2 verification)
89
+ console.log(' Testing DELETE request (simulate disconnect crash)...');
90
+ try {
91
+ // DELETE requests often have no body.
92
+ // The bug was reading req.body.id => crash if req.body is undefined/empty
93
+ await axios_1.default.delete(`${BASE_URL}/mcp`, {
94
+ headers: authHeaders,
95
+ validateStatus: (status) => true // Accept any status
96
+ });
97
+ console.log(' ✅ DELETE request completed without network error (Server survived).');
98
+ }
99
+ catch (error) {
100
+ console.error(' ❌ Server likely crashed on DELETE:', error.message);
101
+ throw error;
102
+ }
103
+ // Verify Server is STILL ALIVE after DELETE
104
+ const healthRes = await axios_1.default.get(`${BASE_URL}/health`);
105
+ node_assert_1.default.strictEqual(healthRes.status, 200, 'Server should be alive after DELETE request');
106
+ console.log(' ✅ Server verified alive after malformed request.');
107
+ // 4. Test SSE Endpoint Handshake
108
+ console.log(' Testing SSE Handshake...');
109
+ const sseRes = await axios_1.default.get(`${BASE_URL}/mcp`, {
110
+ headers: {
111
+ 'Accept': 'text/event-stream',
112
+ ...authHeaders
113
+ },
114
+ responseType: 'stream'
115
+ });
116
+ const stream = sseRes.data;
117
+ let dataReceived = '';
118
+ await new Promise((resolve, reject) => {
119
+ stream.on('data', (chunk) => {
120
+ dataReceived += chunk.toString();
121
+ if (dataReceived.includes('event: endpoint')) {
122
+ stream.destroy();
123
+ resolve();
124
+ }
125
+ });
126
+ stream.on('error', reject);
127
+ setTimeout(() => { stream.destroy(); resolve(); }, 2000);
128
+ });
129
+ node_assert_1.default.ok(dataReceived.includes('event: endpoint'), 'Should receive endpoint event');
130
+ // Verify absolute URL format
131
+ const endpointLine = dataReceived.split('\n').find(l => l.startsWith('data: http'));
132
+ node_assert_1.default.ok(endpointLine, 'Should find data line with absolute URL starting with http');
133
+ node_assert_1.default.ok(endpointLine?.includes(`localhost:${PORT}/mcp`), 'Absolute URL should point to correct port/path');
134
+ console.log(' ✅ SSE Handshake verified with absolute URL.');
135
+ return true;
136
+ }
137
+ catch (error) {
138
+ console.error(' ❌ Test failed:', error.message);
139
+ return false;
140
+ }
141
+ finally {
142
+ if (dbService) {
143
+ const db = dbService.db;
144
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
145
+ await dbService.close();
146
+ }
147
+ if (fraimProcess && fraimProcess.pid) {
148
+ console.log(' Cleanup: Killing server...');
149
+ await new Promise(resolve => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGKILL', () => resolve()));
150
+ }
151
+ }
152
+ }
153
+ const testCases = [
154
+ {
155
+ name: 'MCP Connection & Stability',
156
+ description: 'Verifies connection protocol, notification handling, and crash resilience',
157
+ testFunction: testMcpConnection,
158
+ tags: ['mcp', 'connection']
159
+ }
160
+ ];
161
+ (0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'MCP Connection Reliability')
162
+ .then(() => process.exit(0))
163
+ .catch((err) => {
164
+ console.error('Test runner failed:', err);
165
+ process.exit(1);
166
+ });
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_child_process_1 = require("node:child_process");
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const db_service_js_1 = require("../src/fraim/db-service.js");
9
+ const test_utils_1 = require("./test-utils");
10
+ const node_assert_1 = __importDefault(require("node:assert"));
11
+ const tree_kill_1 = __importDefault(require("tree-kill"));
12
+ const path_1 = __importDefault(require("path"));
13
+ async function testFileIssueToolAPI() {
14
+ console.log(' 🚀 Testing File Issue Tool via MCP API...');
15
+ let fraimProcess;
16
+ let dbService;
17
+ const PORT = 10002; // Different port
18
+ const TEST_API_KEY = 'test-fraim-key-issues';
19
+ const BASE_URL = `http://localhost:${PORT}`;
20
+ try {
21
+ // 1. Seed API key
22
+ dbService = new db_service_js_1.FraimDbService();
23
+ await dbService.connect();
24
+ const db = dbService.db;
25
+ await db.collection('fraim_api_keys').updateOne({ key: TEST_API_KEY }, {
26
+ $set: {
27
+ userId: 'test-user@ashley.ai',
28
+ orgId: 'test-org',
29
+ isActive: true,
30
+ createdAt: new Date()
31
+ }
32
+ }, { upsert: true });
33
+ // 2. Start server
34
+ console.log(` Starting server on port ${PORT}...`);
35
+ const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
36
+ const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
37
+ fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
38
+ env: {
39
+ ...process.env,
40
+ FRAIM_MCP_PORT: PORT.toString(),
41
+ FRAIM_SKIP_INDEX_ON_START: 'true'
42
+ },
43
+ stdio: 'pipe', // Using pipe to keep output cleaner, assuming debug isn't needed unless failure
44
+ shell: true
45
+ });
46
+ // 3. Wait for start
47
+ console.log(' Waiting for server to start...');
48
+ let started = false;
49
+ for (let i = 0; i < 20; i++) {
50
+ try {
51
+ await axios_1.default.get(`${BASE_URL}/health`, { timeout: 1000 });
52
+ started = true;
53
+ console.log(' Server started!');
54
+ break;
55
+ }
56
+ catch (e) {
57
+ await new Promise(resolve => setTimeout(resolve, 1000));
58
+ }
59
+ }
60
+ if (!started) {
61
+ console.error(' ❌ Server failed to start');
62
+ return false;
63
+ }
64
+ // 4. Perform Handshake (Required for Session)
65
+ console.log(' Performing Handshake (fraim_connect)...');
66
+ await axios_1.default.post(`${BASE_URL}/mcp`, {
67
+ jsonrpc: '2.0',
68
+ id: 0,
69
+ method: 'tools/call',
70
+ params: {
71
+ name: 'fraim_connect',
72
+ arguments: {
73
+ machine: { hostname: 'test-host', platform: process.platform },
74
+ repo: { url: 'https://github.com/test/repo' }
75
+ }
76
+ }
77
+ }, {
78
+ headers: { 'x-api-key': TEST_API_KEY }
79
+ });
80
+ // 5. Test file_issue tool call (Dry Run)
81
+ console.log(' Testing file_issue tool (dry run)...');
82
+ const response = await axios_1.default.post(`${BASE_URL}/mcp`, {
83
+ jsonrpc: '2.0',
84
+ id: 1,
85
+ method: 'tools/call',
86
+ params: {
87
+ name: 'file_issue',
88
+ arguments: {
89
+ title: 'Integration Test Issue',
90
+ body: 'This is an integration test',
91
+ dryRun: true
92
+ }
93
+ }
94
+ }, {
95
+ headers: { 'x-api-key': TEST_API_KEY },
96
+ timeout: 10000
97
+ });
98
+ node_assert_1.default.strictEqual(response.status, 200);
99
+ node_assert_1.default.ok(response.data.result, 'Should have result');
100
+ node_assert_1.default.ok(response.data.result.content, 'Should have content');
101
+ const contentText = response.data.result.content[0].text;
102
+ const resultJson = JSON.parse(contentText);
103
+ node_assert_1.default.strictEqual(resultJson.success, true);
104
+ node_assert_1.default.strictEqual(resultJson.dryRun, true);
105
+ node_assert_1.default.ok(resultJson.message.includes('[DRY RUN]'));
106
+ node_assert_1.default.ok(resultJson.message.includes('Integration Test Issue'));
107
+ console.log(' ✅ file_issue tool verified successfully');
108
+ return true;
109
+ }
110
+ catch (error) {
111
+ console.error(' ❌ Test failed:', error);
112
+ if (axios_1.default.isAxiosError(error) && error.response) {
113
+ console.error(' Response data:', error.response.data);
114
+ }
115
+ return false;
116
+ }
117
+ finally {
118
+ console.log(' Cleaning up...');
119
+ if (dbService) {
120
+ const db = dbService.db;
121
+ if (db) {
122
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
123
+ }
124
+ await dbService.close().catch(() => { });
125
+ }
126
+ if (fraimProcess && fraimProcess.pid) {
127
+ const pid = fraimProcess.pid;
128
+ await new Promise((resolve) => (0, tree_kill_1.default)(pid, 'SIGKILL', () => resolve()));
129
+ console.log(` Terminated server process ${pid}`);
130
+ }
131
+ }
132
+ }
133
+ async function runTest(testCase) {
134
+ return await testCase.testFunction();
135
+ }
136
+ const testCases = [
137
+ {
138
+ name: 'File Issue Integration Test',
139
+ description: 'Tests file_issue tool via real MCP server request',
140
+ testFunction: testFileIssueToolAPI,
141
+ tags: ['integration', 'issues']
142
+ }
143
+ ];
144
+ (0, test_utils_1.runTests)(testCases, runTest, 'Fraim Issue API Integration');
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_child_process_1 = require("node:child_process");
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const test_utils_1 = require("./test-utils");
9
+ const node_assert_1 = __importDefault(require("node:assert"));
10
+ const tree_kill_1 = __importDefault(require("tree-kill"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const db_service_js_1 = require("../src/fraim/db-service.js");
13
+ /**
14
+ * Test that resources/list and prompts/list work without session
15
+ * This verifies the fix for the MCP telemetry blocking issue
16
+ */
17
+ async function testMcpLifecycleMethods() {
18
+ console.log(' 🚀 Testing MCP Lifecycle Methods (resources/list, prompts/list)...');
19
+ let fraimProcess;
20
+ let dbService;
21
+ const PORT = Math.floor(Math.random() * 1000) + 13000;
22
+ const BASE_URL = `http://localhost:${PORT}`;
23
+ const TEST_API_KEY = 'test-fraim-key-lifecycle';
24
+ const TEST_ADMIN_KEY = 'test-admin-key-lifecycle';
25
+ try {
26
+ // 0. Setup DB and Key
27
+ dbService = new db_service_js_1.FraimDbService();
28
+ await dbService.connect();
29
+ const db = dbService.db;
30
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY });
31
+ await db.collection('fraim_api_keys').insertOne({
32
+ key: TEST_API_KEY,
33
+ userId: 'test-user-lifecycle',
34
+ orgId: 'test-org',
35
+ isActive: true,
36
+ createdAt: new Date()
37
+ });
38
+ // 1. Start Server
39
+ console.log(` Starting server on port ${PORT}...`);
40
+ const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
41
+ const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
42
+ fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
43
+ env: {
44
+ ...process.env,
45
+ FRAIM_MCP_PORT: PORT.toString(),
46
+ FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
47
+ FRAIM_SKIP_INDEX_ON_START: 'true'
48
+ },
49
+ stdio: 'pipe',
50
+ shell: true
51
+ });
52
+ if (fraimProcess.stdout)
53
+ fraimProcess.stdout.pipe(process.stdout);
54
+ if (fraimProcess.stderr)
55
+ fraimProcess.stderr.pipe(process.stderr);
56
+ // Wait for start
57
+ let started = false;
58
+ for (let i = 0; i < 15; i++) {
59
+ try {
60
+ await axios_1.default.get(`${BASE_URL}/health`, { timeout: 500 });
61
+ started = true;
62
+ break;
63
+ }
64
+ catch (e) {
65
+ await new Promise(resolve => setTimeout(resolve, 1000));
66
+ }
67
+ }
68
+ if (!started)
69
+ throw new Error('Server failed to start');
70
+ console.log(' Server started!');
71
+ const authHeaders = { 'x-api-key': TEST_API_KEY };
72
+ // 2. Test initialize (should work without session)
73
+ console.log(' Testing initialize...');
74
+ const initRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
75
+ jsonrpc: '2.0',
76
+ method: 'initialize',
77
+ params: {
78
+ protocolVersion: '2024-11-05',
79
+ capabilities: {},
80
+ clientInfo: { name: 'test-client', version: '1.0.0' }
81
+ },
82
+ id: 1
83
+ }, { headers: authHeaders });
84
+ node_assert_1.default.strictEqual(initRes.status, 200, 'initialize should return 200');
85
+ node_assert_1.default.ok(initRes.data.result, 'initialize should return result');
86
+ node_assert_1.default.ok(initRes.data.result.serverInfo, 'initialize should return serverInfo');
87
+ console.log(' ✅ initialize works without session');
88
+ // 3. Test tools/list (should work without session)
89
+ console.log(' Testing tools/list...');
90
+ const toolsRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
91
+ jsonrpc: '2.0',
92
+ method: 'tools/list',
93
+ params: {},
94
+ id: 2
95
+ }, { headers: authHeaders });
96
+ node_assert_1.default.strictEqual(toolsRes.status, 200, 'tools/list should return 200');
97
+ node_assert_1.default.ok(toolsRes.data.result, 'tools/list should return result');
98
+ node_assert_1.default.ok(Array.isArray(toolsRes.data.result.tools), 'tools/list should return tools array');
99
+ node_assert_1.default.ok(toolsRes.data.result.tools.length > 0, 'tools/list should return at least one tool');
100
+ console.log(` ✅ tools/list works without session (${toolsRes.data.result.tools.length} tools)`);
101
+ // 4. Test resources/list (THE FIX - should work without session)
102
+ console.log(' Testing resources/list (THE FIX)...');
103
+ const resourcesRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
104
+ jsonrpc: '2.0',
105
+ method: 'resources/list',
106
+ params: {},
107
+ id: 3
108
+ }, { headers: authHeaders });
109
+ node_assert_1.default.strictEqual(resourcesRes.status, 200, 'resources/list should return 200');
110
+ node_assert_1.default.ok(resourcesRes.data.result, 'resources/list should return result');
111
+ node_assert_1.default.ok(Array.isArray(resourcesRes.data.result.resources), 'resources/list should return resources array');
112
+ node_assert_1.default.strictEqual(resourcesRes.data.result.resources.length, 0, 'resources/list should return empty array');
113
+ console.log(' ✅ resources/list works without session (returns empty array)');
114
+ // 5. Test prompts/list (THE FIX - should work without session)
115
+ console.log(' Testing prompts/list (THE FIX)...');
116
+ const promptsRes = await axios_1.default.post(`${BASE_URL}/mcp`, {
117
+ jsonrpc: '2.0',
118
+ method: 'prompts/list',
119
+ params: {},
120
+ id: 4
121
+ }, { headers: authHeaders });
122
+ node_assert_1.default.strictEqual(promptsRes.status, 200, 'prompts/list should return 200');
123
+ node_assert_1.default.ok(promptsRes.data.result, 'prompts/list should return result');
124
+ node_assert_1.default.ok(Array.isArray(promptsRes.data.result.prompts), 'prompts/list should return prompts array');
125
+ node_assert_1.default.strictEqual(promptsRes.data.result.prompts.length, 0, 'prompts/list should return empty array');
126
+ console.log(' ✅ prompts/list works without session (returns empty array)');
127
+ // 6. Test that non-lifecycle methods still require session
128
+ console.log(' Testing that non-lifecycle methods require session...');
129
+ try {
130
+ await axios_1.default.post(`${BASE_URL}/mcp`, {
131
+ jsonrpc: '2.0',
132
+ method: 'tools/call',
133
+ params: {
134
+ name: 'get_fraim_workflow',
135
+ arguments: { workflow: 'spec' }
136
+ },
137
+ id: 5
138
+ }, { headers: authHeaders, timeout: 2000 });
139
+ console.error(' ❌ Should have failed with 400 (Session Not Started)');
140
+ return false;
141
+ }
142
+ catch (error) {
143
+ if (error.response) {
144
+ node_assert_1.default.strictEqual(error.response.status, 400, 'Should return 400 for tool call without session');
145
+ node_assert_1.default.match(error.response.data?.error?.message, /Session Not Started/, 'Should mention Session Not Started');
146
+ console.log(' ✅ Non-lifecycle methods still require session');
147
+ }
148
+ else {
149
+ throw error;
150
+ }
151
+ }
152
+ // 7. Test complete MCP initialization sequence
153
+ console.log(' Testing complete MCP initialization sequence...');
154
+ const sequence = [
155
+ { method: 'initialize', id: 10 },
156
+ { method: 'notifications/initialized', id: 11 },
157
+ { method: 'tools/list', id: 12 },
158
+ { method: 'resources/list', id: 13 },
159
+ { method: 'prompts/list', id: 14 }
160
+ ];
161
+ for (const step of sequence) {
162
+ const res = await axios_1.default.post(`${BASE_URL}/mcp`, {
163
+ jsonrpc: '2.0',
164
+ method: step.method,
165
+ params: {},
166
+ id: step.id
167
+ }, { headers: authHeaders });
168
+ node_assert_1.default.strictEqual(res.status, 200, `${step.method} should return 200 in sequence`);
169
+ console.log(` ✅ ${step.method} succeeded in sequence`);
170
+ }
171
+ console.log(' ✅ Complete MCP initialization sequence works without session');
172
+ return true;
173
+ }
174
+ catch (error) {
175
+ console.error(' ❌ Test failed:', error.message);
176
+ if (error.response?.data) {
177
+ console.error(' 🔍 Server Error Detail:', JSON.stringify(error.response.data, null, 2));
178
+ }
179
+ return false;
180
+ }
181
+ finally {
182
+ if (dbService) {
183
+ const db = dbService.db;
184
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
185
+ await dbService.close();
186
+ }
187
+ if (fraimProcess && fraimProcess.pid) {
188
+ console.log(' Cleanup: Killing server...');
189
+ await new Promise(resolve => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGKILL', () => resolve()));
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Test that bootstrap tools work without session
195
+ */
196
+ async function testBootstrapTools() {
197
+ console.log(' 🚀 Testing Bootstrap Tools (without session)...');
198
+ let fraimProcess;
199
+ let dbService;
200
+ const PORT = Math.floor(Math.random() * 1000) + 14000;
201
+ const BASE_URL = `http://localhost:${PORT}`;
202
+ const TEST_API_KEY = 'test-fraim-key-bootstrap';
203
+ const TEST_ADMIN_KEY = 'test-admin-key-bootstrap';
204
+ try {
205
+ // 0. Setup DB and Key
206
+ dbService = new db_service_js_1.FraimDbService();
207
+ await dbService.connect();
208
+ const db = dbService.db;
209
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY });
210
+ await db.collection('fraim_api_keys').insertOne({
211
+ key: TEST_API_KEY,
212
+ userId: 'test-user-bootstrap',
213
+ orgId: 'test-org',
214
+ isActive: true,
215
+ createdAt: new Date()
216
+ });
217
+ // 1. Start Server
218
+ console.log(` Starting server on port ${PORT}...`);
219
+ const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
220
+ const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.ts');
221
+ fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['tsx', `"${serverScript}"`], {
222
+ env: {
223
+ ...process.env,
224
+ FRAIM_MCP_PORT: PORT.toString(),
225
+ FRAIM_ADMIN_KEY: TEST_ADMIN_KEY,
226
+ FRAIM_SKIP_INDEX_ON_START: 'true'
227
+ },
228
+ stdio: 'pipe',
229
+ shell: true
230
+ });
231
+ if (fraimProcess.stdout)
232
+ fraimProcess.stdout.pipe(process.stdout);
233
+ if (fraimProcess.stderr)
234
+ fraimProcess.stderr.pipe(process.stderr);
235
+ // Wait for start
236
+ let started = false;
237
+ for (let i = 0; i < 15; i++) {
238
+ try {
239
+ await axios_1.default.get(`${BASE_URL}/health`, { timeout: 500 });
240
+ started = true;
241
+ break;
242
+ }
243
+ catch (e) {
244
+ await new Promise(resolve => setTimeout(resolve, 1000));
245
+ }
246
+ }
247
+ if (!started)
248
+ throw new Error('Server failed to start');
249
+ console.log(' Server started!');
250
+ const authHeaders = { 'x-api-key': TEST_API_KEY };
251
+ // Test bootstrap tools that should work without session
252
+ const bootstrapTools = [
253
+ { name: 'get_fraim_init', args: {} },
254
+ { name: 'list_fraim_workflows', args: {} },
255
+ { name: 'fraim_get_local_config', args: {} }
256
+ ];
257
+ for (const tool of bootstrapTools) {
258
+ console.log(` Testing ${tool.name}...`);
259
+ const res = await axios_1.default.post(`${BASE_URL}/mcp`, {
260
+ jsonrpc: '2.0',
261
+ method: 'tools/call',
262
+ params: {
263
+ name: tool.name,
264
+ arguments: tool.args
265
+ },
266
+ id: 1
267
+ }, { headers: authHeaders });
268
+ node_assert_1.default.strictEqual(res.status, 200, `${tool.name} should return 200 without session`);
269
+ node_assert_1.default.ok(res.data.result, `${tool.name} should return result`);
270
+ console.log(` ✅ ${tool.name} works without session`);
271
+ }
272
+ return true;
273
+ }
274
+ catch (error) {
275
+ console.error(' ❌ Test failed:', error.message);
276
+ if (error.response?.data) {
277
+ console.error(' 🔍 Server Error Detail:', JSON.stringify(error.response.data, null, 2));
278
+ }
279
+ return false;
280
+ }
281
+ finally {
282
+ if (dbService) {
283
+ const db = dbService.db;
284
+ await db.collection('fraim_api_keys').deleteOne({ key: TEST_API_KEY }).catch(() => { });
285
+ await dbService.close();
286
+ }
287
+ if (fraimProcess && fraimProcess.pid) {
288
+ console.log(' Cleanup: Killing server...');
289
+ await new Promise(resolve => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGKILL', () => resolve()));
290
+ }
291
+ }
292
+ }
293
+ const testCases = [
294
+ {
295
+ name: 'MCP Lifecycle Methods',
296
+ description: 'Tests that resources/list and prompts/list work without session (fix for telemetry blocking)',
297
+ testFunction: testMcpLifecycleMethods,
298
+ tags: ['mcp', 'lifecycle', 'telemetry']
299
+ },
300
+ {
301
+ name: 'Bootstrap Tools',
302
+ description: 'Tests that bootstrap tools work without session',
303
+ testFunction: testBootstrapTools,
304
+ tags: ['mcp', 'bootstrap', 'telemetry']
305
+ }
306
+ ];
307
+ (0, test_utils_1.runTests)(testCases, async (t) => t.testFunction(), 'MCP Lifecycle Methods & Bootstrap Tools')
308
+ .then(() => process.exit(0))
309
+ .catch((err) => {
310
+ console.error('Test runner failed:', err);
311
+ process.exit(1);
312
+ });