fa-mcp-sdk 0.4.3 → 0.4.6

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 (153) hide show
  1. package/bin/fa-mcp.js +1040 -1039
  2. package/cli-template/eslint.config.js +16 -136
  3. package/cli-template/package.json +9 -10
  4. package/cli-template/tsconfig.json +1 -0
  5. package/dist/core/_types_/active-directory-config.d.ts.map +1 -1
  6. package/dist/core/_types_/config.d.ts +1 -1
  7. package/dist/core/_types_/config.d.ts.map +1 -1
  8. package/dist/core/_types_/types.d.ts.map +1 -1
  9. package/dist/core/ad/group-checker.d.ts.map +1 -1
  10. package/dist/core/ad/group-checker.js.map +1 -1
  11. package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
  12. package/dist/core/agent-tester/agent-tester-router.js +8 -8
  13. package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
  14. package/dist/core/agent-tester/check-llm.d.ts.map +1 -1
  15. package/dist/core/agent-tester/check-llm.js +1 -1
  16. package/dist/core/agent-tester/check-llm.js.map +1 -1
  17. package/dist/core/agent-tester/services/TesterAgentService.d.ts.map +1 -1
  18. package/dist/core/agent-tester/services/TesterAgentService.js +53 -53
  19. package/dist/core/agent-tester/services/TesterAgentService.js.map +1 -1
  20. package/dist/core/agent-tester/services/TesterMcpClientService.d.ts.map +1 -1
  21. package/dist/core/agent-tester/services/TesterMcpClientService.js +2 -2
  22. package/dist/core/agent-tester/services/TesterMcpClientService.js.map +1 -1
  23. package/dist/core/auth/admin-auth.d.ts.map +1 -1
  24. package/dist/core/auth/admin-auth.js +3 -3
  25. package/dist/core/auth/admin-auth.js.map +1 -1
  26. package/dist/core/auth/basic.d.ts.map +1 -1
  27. package/dist/core/auth/basic.js.map +1 -1
  28. package/dist/core/auth/jwt.d.ts.map +1 -1
  29. package/dist/core/auth/jwt.js +6 -16
  30. package/dist/core/auth/jwt.js.map +1 -1
  31. package/dist/core/auth/middleware.d.ts.map +1 -1
  32. package/dist/core/auth/middleware.js +3 -2
  33. package/dist/core/auth/middleware.js.map +1 -1
  34. package/dist/core/auth/multi-auth.d.ts +0 -3
  35. package/dist/core/auth/multi-auth.d.ts.map +1 -1
  36. package/dist/core/auth/multi-auth.js +10 -7
  37. package/dist/core/auth/multi-auth.js.map +1 -1
  38. package/dist/core/auth/permanent.d.ts.map +1 -1
  39. package/dist/core/auth/permanent.js +1 -1
  40. package/dist/core/auth/permanent.js.map +1 -1
  41. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.d.ts.map +1 -1
  42. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js +2 -2
  43. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js.map +1 -1
  44. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.d.ts.map +1 -1
  45. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js +1 -1
  46. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js.map +1 -1
  47. package/dist/core/auth/token-generator/ntlm/ntlm-integration.d.ts.map +1 -1
  48. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js +1 -1
  49. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js.map +1 -1
  50. package/dist/core/auth/token-generator/ntlm/ntlm-templates.d.ts.map +1 -1
  51. package/dist/core/auth/token-generator/ntlm/ntlm-templates.js +222 -221
  52. package/dist/core/auth/token-generator/ntlm/ntlm-templates.js.map +1 -1
  53. package/dist/core/auth/token-generator/server.d.ts.map +1 -1
  54. package/dist/core/auth/token-generator/server.js +8 -8
  55. package/dist/core/auth/token-generator/server.js.map +1 -1
  56. package/dist/core/bootstrap/init-config.d.ts.map +1 -1
  57. package/dist/core/bootstrap/init-config.js +4 -4
  58. package/dist/core/bootstrap/init-config.js.map +1 -1
  59. package/dist/core/bootstrap/startup-info.d.ts.map +1 -1
  60. package/dist/core/bootstrap/startup-info.js +4 -4
  61. package/dist/core/bootstrap/startup-info.js.map +1 -1
  62. package/dist/core/cache/cache.d.ts.map +1 -1
  63. package/dist/core/cache/cache.js +3 -3
  64. package/dist/core/cache/cache.js.map +1 -1
  65. package/dist/core/consul/access-points-updater.d.ts.map +1 -1
  66. package/dist/core/consul/access-points-updater.js +3 -3
  67. package/dist/core/consul/access-points-updater.js.map +1 -1
  68. package/dist/core/consul/deregister.d.ts.map +1 -1
  69. package/dist/core/consul/deregister.js +1 -1
  70. package/dist/core/consul/deregister.js.map +1 -1
  71. package/dist/core/consul/get-consul-api.d.ts.map +1 -1
  72. package/dist/core/consul/get-consul-api.js +3 -3
  73. package/dist/core/consul/get-consul-api.js.map +1 -1
  74. package/dist/core/db/pg-db.d.ts +1 -1
  75. package/dist/core/db/pg-db.d.ts.map +1 -1
  76. package/dist/core/db/pg-db.js +2 -2
  77. package/dist/core/db/pg-db.js.map +1 -1
  78. package/dist/core/debug.js +1 -1
  79. package/dist/core/debug.js.map +1 -1
  80. package/dist/core/init-mcp-server.d.ts.map +1 -1
  81. package/dist/core/init-mcp-server.js +9 -9
  82. package/dist/core/init-mcp-server.js.map +1 -1
  83. package/dist/core/logger.d.ts.map +1 -1
  84. package/dist/core/logger.js +3 -3
  85. package/dist/core/logger.js.map +1 -1
  86. package/dist/core/mcp/create-mcp-server.d.ts.map +1 -1
  87. package/dist/core/mcp/create-mcp-server.js +1 -1
  88. package/dist/core/mcp/create-mcp-server.js.map +1 -1
  89. package/dist/core/mcp/prompts.d.ts.map +1 -1
  90. package/dist/core/mcp/prompts.js +1 -3
  91. package/dist/core/mcp/prompts.js.map +1 -1
  92. package/dist/core/mcp/resources.d.ts.map +1 -1
  93. package/dist/core/mcp/resources.js +8 -10
  94. package/dist/core/mcp/resources.js.map +1 -1
  95. package/dist/core/mcp/server-stdio.d.ts.map +1 -1
  96. package/dist/core/mcp/server-stdio.js.map +1 -1
  97. package/dist/core/utils/formatToolResult.d.ts.map +1 -1
  98. package/dist/core/utils/formatToolResult.js +1 -3
  99. package/dist/core/utils/formatToolResult.js.map +1 -1
  100. package/dist/core/utils/port-checker.d.ts.map +1 -1
  101. package/dist/core/utils/port-checker.js +1 -1
  102. package/dist/core/utils/port-checker.js.map +1 -1
  103. package/dist/core/utils/rate-limit.js +2 -2
  104. package/dist/core/utils/testing/McpSseClient.d.ts.map +1 -1
  105. package/dist/core/utils/testing/McpSseClient.js.map +1 -1
  106. package/dist/core/utils/testing/McpStdioClient.d.ts.map +1 -1
  107. package/dist/core/utils/testing/McpStdioClient.js.map +1 -1
  108. package/dist/core/utils/utils.d.ts.map +1 -1
  109. package/dist/core/utils/utils.js.map +1 -1
  110. package/dist/core/web/admin-router.d.ts.map +1 -1
  111. package/dist/core/web/admin-router.js +4 -4
  112. package/dist/core/web/admin-router.js.map +1 -1
  113. package/dist/core/web/cors.d.ts.map +1 -1
  114. package/dist/core/web/cors.js.map +1 -1
  115. package/dist/core/web/favicon-svg.d.ts.map +1 -1
  116. package/dist/core/web/favicon-svg.js.map +1 -1
  117. package/dist/core/web/home-api.d.ts.map +1 -1
  118. package/dist/core/web/home-api.js +4 -4
  119. package/dist/core/web/home-api.js.map +1 -1
  120. package/dist/core/web/openapi.d.ts.map +1 -1
  121. package/dist/core/web/openapi.js.map +1 -1
  122. package/dist/core/web/server-http.d.ts.map +1 -1
  123. package/dist/core/web/server-http.js +20 -22
  124. package/dist/core/web/server-http.js.map +1 -1
  125. package/dist/core/web/static/agent-tester/script.js +1503 -1513
  126. package/dist/core/web/static/home/script.js +646 -646
  127. package/dist/core/web/static/token-gen/script.js +561 -561
  128. package/dist/core/web/svg-icons.d.ts.map +1 -1
  129. package/dist/core/web/svg-icons.js +1 -1
  130. package/dist/core/web/svg-icons.js.map +1 -1
  131. package/package.json +2 -6
  132. package/scripts/copy-static.js +31 -31
  133. package/scripts/kill-port.js +107 -107
  134. package/scripts/npm/patch_node_modules.js +8 -8
  135. package/scripts/npm/run.js +31 -31
  136. package/scripts/remove-nul.js +53 -53
  137. package/scripts/update-doc.js +18 -18
  138. package/src/template/_types_/custom-config.ts +83 -83
  139. package/src/template/api/router.ts +86 -89
  140. package/src/template/custom-resources.ts +11 -11
  141. package/src/template/prompts/agent-brief.ts +8 -8
  142. package/src/template/prompts/agent-prompt.ts +10 -10
  143. package/src/template/prompts/custom-prompts.ts +12 -12
  144. package/src/template/start.ts +71 -72
  145. package/src/template/tools/handle-tool-call.ts +57 -56
  146. package/src/template/tools/tools.ts +89 -88
  147. package/src/tests/jest-simple-reporter.js +10 -10
  148. package/src/tests/mcp/sse/test-sse-npm-package.js +96 -96
  149. package/src/tests/mcp/test-cases.js +143 -143
  150. package/src/tests/mcp/test-http.js +76 -75
  151. package/src/tests/mcp/test-sse.js +80 -79
  152. package/src/tests/mcp/test-stdio.js +83 -81
  153. package/src/tests/utils.ts +157 -156
@@ -1,81 +1,83 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * STDIO transport tests for the template MCP server (src/template)
5
- * Uses a minimal NDJSON JSON-RPC client over child_process stdio
6
- */
7
-
8
- import { spawn } from 'child_process';
9
- import { fileURLToPath } from 'url';
10
- import { dirname, join } from 'path';
11
- import TEMPLATE_TESTS from './test-cases.js';
12
- import { McpStdioClient } from '../../../dist/core/index.js';
13
-
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = dirname(__filename);
16
-
17
- const serverPath = join(__dirname, '../../../dist/template/start.js');
18
-
19
- async function runTestGroup (title, tests, client) {
20
- console.log(`\n${title}:`);
21
- let passed = 0;
22
- for (const test of tests) {
23
- const name = (await test).name || 'test';
24
- try {
25
- const res = await test(client);
26
- if (res.passed) {
27
- console.log(` ✅ ${res.name}`);
28
- passed++;
29
- } else {
30
- console.log(` ❌ ${res.name}`);
31
- if (res.details) {
32
- console.log(' ', res.details);
33
- }
34
- }
35
- } catch (e) {
36
- console.log(` ❌ ${name}:`, e.message);
37
- }
38
- }
39
- console.log(` Result: ${passed}/${tests.length} passed`);
40
- return passed;
41
- }
42
-
43
- async function main () {
44
- console.log('🧪 STDIO tests for template MCP server');
45
- console.log('='.repeat(60));
46
-
47
- const proc = spawn('node', [serverPath, 'stdio'], {
48
- stdio: ['pipe', 'pipe', 'pipe'],
49
- env: { ...process.env, NODE_ENV: 'test' },
50
- });
51
-
52
- const client = new McpStdioClient(proc);
53
-
54
- try {
55
- // Initialize handshake (optional for stdio server; safe to send)
56
- await client.send('initialize', {
57
- protocolVersion: '2024-11-05',
58
- capabilities: { tools: {} },
59
- clientInfo: { name: 'stdio-test', version: '1.0.0' },
60
- }).catch(() => undefined);
61
-
62
- const p1 = await runTestGroup('Prompts', TEMPLATE_TESTS.prompts, client);
63
- const p2 = await runTestGroup('Resources', TEMPLATE_TESTS.resources, client);
64
- const p3 = await runTestGroup('Tools', TEMPLATE_TESTS.tools, client);
65
-
66
- const total = TEMPLATE_TESTS.prompts.length + TEMPLATE_TESTS.resources.length + TEMPLATE_TESTS.tools.length;
67
- const sum = p1 + p2 + p3;
68
- console.log(`\nSummary: ${sum}/${total} tests passed`);
69
- } finally {
70
- try { proc.kill(); } catch {}
71
- }
72
- }
73
-
74
- main()
75
- .then(() => {
76
- process.exit(0);
77
- })
78
- .catch((e) => {
79
- console.error('Test failed:', e?.message || e);
80
- process.exit(1);
81
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * STDIO transport tests for the template MCP server (src/template)
5
+ * Uses a minimal NDJSON JSON-RPC client over child_process stdio
6
+ */
7
+
8
+ import { spawn } from 'child_process';
9
+ import { dirname, join } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ import { McpStdioClient } from '../../../dist/core/index.js';
13
+
14
+ import TEMPLATE_TESTS from './test-cases.js';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+
19
+ const serverPath = join(__dirname, '../../../dist/template/start.js');
20
+
21
+ async function runTestGroup (title, tests, client) {
22
+ console.log(`\n${title}:`);
23
+ let passed = 0;
24
+ for (const test of tests) {
25
+ const name = (await test).name || 'test';
26
+ try {
27
+ const res = await test(client);
28
+ if (res.passed) {
29
+ console.log(` ✅ ${res.name}`);
30
+ passed++;
31
+ } else {
32
+ console.log(` ❌ ${res.name}`);
33
+ if (res.details) {
34
+ console.log(' ', res.details);
35
+ }
36
+ }
37
+ } catch (e) {
38
+ console.log(` ❌ ${name}:`, e.message);
39
+ }
40
+ }
41
+ console.log(` Result: ${passed}/${tests.length} passed`);
42
+ return passed;
43
+ }
44
+
45
+ async function main () {
46
+ console.log('🧪 STDIO tests for template MCP server');
47
+ console.log('='.repeat(60));
48
+
49
+ const proc = spawn('node', [serverPath, 'stdio'], {
50
+ stdio: ['pipe', 'pipe', 'pipe'],
51
+ env: { ...process.env, NODE_ENV: 'test' },
52
+ });
53
+
54
+ const client = new McpStdioClient(proc);
55
+
56
+ try {
57
+ // Initialize handshake (optional for stdio server; safe to send)
58
+ await client.send('initialize', {
59
+ protocolVersion: '2024-11-05',
60
+ capabilities: { tools: {} },
61
+ clientInfo: { name: 'stdio-test', version: '1.0.0' },
62
+ }).catch(() => undefined);
63
+
64
+ const p1 = await runTestGroup('Prompts', TEMPLATE_TESTS.prompts, client);
65
+ const p2 = await runTestGroup('Resources', TEMPLATE_TESTS.resources, client);
66
+ const p3 = await runTestGroup('Tools', TEMPLATE_TESTS.tools, client);
67
+
68
+ const total = TEMPLATE_TESTS.prompts.length + TEMPLATE_TESTS.resources.length + TEMPLATE_TESTS.tools.length;
69
+ const sum = p1 + p2 + p3;
70
+ console.log(`\nSummary: ${sum}/${total} tests passed`);
71
+ } finally {
72
+ try { proc.kill(); } catch {}
73
+ }
74
+ }
75
+
76
+ main()
77
+ .then(() => {
78
+ process.exit(0);
79
+ })
80
+ .catch((e) => {
81
+ console.error('Test failed:', e?.message || e);
82
+ process.exit(1);
83
+ });
@@ -1,156 +1,157 @@
1
- import fsp from 'fs/promises';
2
- import fss from 'fs';
3
- import path from 'path';
4
- import chalk from 'chalk';
5
- import * as dotenv from 'dotenv';
6
-
7
- dotenv.config({ quiet: true, path: path.join(process.cwd(), '.env') });
8
-
9
- const testResultLogsDir = process.env.TEST_RESULT_LOGS_DIR || '_logs/mcp';
10
-
11
- const RESULTS_DIR = path.join(process.cwd(), testResultLogsDir);
12
-
13
- if (!fss.existsSync(RESULTS_DIR)) {
14
- fss.mkdirSync(RESULTS_DIR, { recursive: true });
15
- }
16
-
17
- export interface ITestResult {
18
- // Test / tool identifiers
19
- fullId: string;
20
- toolName: string;
21
- description: string;
22
-
23
- // Tool invocation parameters
24
- parameters: unknown | null;
25
-
26
- // Temporal metadata
27
- timestamp: string; // ISO string
28
- duration: number; // milliseconds
29
-
30
- // Execution status
31
- status: 'pending' | 'passed' | 'failed' | 'skipped' | 'expected_failure';
32
-
33
- // Marker icon for logs (may be absent in "pending")
34
- marker?: string;
35
-
36
- // MCP response
37
- response: unknown | null;
38
-
39
- // Error (human-readable message)
40
- error: string | null;
41
-
42
- // Additional error details (structured)
43
- errorDetails?: unknown | null;
44
-
45
- // Full MCP response on error (JSON-RPC response)
46
- fullMcpResponse?: unknown;
47
-
48
- // Request headers used for the MCP server call
49
- requestHeaders?: Record<string, string>;
50
- }
51
-
52
- /**
53
- * Format test result as Markdown
54
- */
55
- export const formatResultAsMarkdown = (result: ITestResult) => {
56
- const t = '```';
57
- const mdText = (s: string | null) => `${t}\n${s}\n${t}`;
58
- const mdDescr = (s: string) => `${t}description\n${s}\n${t}`;
59
- const mdJson = (v: any) => `${t}json\n${v && JSON.stringify(v, null, 2)}\n${t}`;
60
-
61
- let resultStatus = '⚠️ RESULT STATUS UNKNOWN';
62
- let errorText = '';
63
- // md += `## Response\n\n\`\`\`json\n${JSON.stringify(result.response, null, 2)}\n\`\`\`\n\n`;
64
-
65
- if (result.status === 'passed') {
66
- resultStatus = '✅ PASSED';
67
- } else {
68
- // Show full MCP response as seen by the agent, or fallback to separate sections
69
- if (result.fullMcpResponse) {
70
- errorText = `## MCP Response (as seen by agent)\n\n${mdJson(result.fullMcpResponse)}\n\n`;
71
- } else {
72
- errorText = `## Error\n\n${mdText(result.error)}\n\n`;
73
- // Add detailed error information if available
74
- if (result.errorDetails) {
75
- errorText += `## Error Details\n\n${mdJson(result.errorDetails)}\n\n`;
76
- }
77
- }
78
- if (result.status === 'expected_failure') {
79
- resultStatus = '⚠️ Expected failure - test validation successful';
80
- } else {
81
- resultStatus = '❌ FAILED';
82
- }
83
- }
84
-
85
- let requestHeaders = '';
86
- if (result.requestHeaders && Object.keys(result.requestHeaders).length > 0) {
87
- requestHeaders = `\nHeaders:\n${Object.entries(result.requestHeaders).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n`;
88
- }
89
-
90
- // Format response section
91
- let responseText = '';
92
- if (result.response !== null && result.response !== undefined) {
93
- try {
94
- let parsedResponse: any = result.response;
95
- let isJsonParsed = false;
96
-
97
- // If response is a string, try to parse as JSON first
98
- if (typeof result.response === 'string') {
99
- try {
100
- parsedResponse = JSON.parse(result.response);
101
- isJsonParsed = true;
102
- } catch {
103
- // If not valid JSON, treat as text
104
- responseText = `## Response\n\n${mdText(result.response)}\n\n`;
105
- }
106
- } else if (typeof result.response === 'object') {
107
- isJsonParsed = true;
108
- }
109
-
110
- // If we have a successfully parsed or original object
111
- if (isJsonParsed && typeof parsedResponse === 'object') {
112
- let text = parsedResponse;
113
- let addText = '';
114
- // Check if response has content[0].text structure and extract text
115
- if (Array.isArray(parsedResponse?.content) && parsedResponse.content[0]?.text) {
116
- const textContent = parsedResponse.content[0].text;
117
- parsedResponse.content[0].text = '📋';
118
- text = parsedResponse;
119
- addText = `## Formatted Text 📋\n${mdText(textContent)}\n\n`;
120
- }
121
- responseText = `## Response\n\n${mdJson(text)}\n\n${addText}`;
122
- }
123
-
124
- } catch {
125
- // Fallback to text if any parsing errors
126
- responseText = `## Response\n\n${mdText(String(result.response))}\n\n`;
127
- }
128
- }
129
-
130
- return `${resultStatus} / ${result.timestamp} / ${result.duration}ms
131
- # ${result.toolName}
132
- ${requestHeaders}
133
- ${mdDescr(result.description)}
134
-
135
- parameters:
136
- ${mdJson(result.parameters)}
137
-
138
- ${responseText}${errorText}`;
139
- };
140
-
141
- /**
142
- * Log test result to individual file
143
- */
144
- export const logResultToFile = async (result: ITestResult) => {
145
- // const filename = `${result.fullId}_${result.toolName}.md`;
146
- const filename = `${result.toolName}.md`;
147
- const filepath = path.join(RESULTS_DIR, filename);
148
-
149
- const content = formatResultAsMarkdown(result);
150
-
151
- try {
152
- await fsp.writeFile(filepath, content, 'utf-8');
153
- } catch (error: Error | any) {
154
- console.log(chalk.red(` Failed to write log file: ${error.message}`));
155
- }
156
- };
1
+ import fss from 'fs';
2
+ import fsp from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ import chalk from 'chalk';
6
+ import * as dotenv from 'dotenv';
7
+
8
+ dotenv.config({ quiet: true, path: path.join(process.cwd(), '.env') });
9
+
10
+ const testResultLogsDir = process.env.TEST_RESULT_LOGS_DIR || '_logs/mcp';
11
+
12
+ const RESULTS_DIR = path.join(process.cwd(), testResultLogsDir);
13
+
14
+ if (!fss.existsSync(RESULTS_DIR)) {
15
+ fss.mkdirSync(RESULTS_DIR, { recursive: true });
16
+ }
17
+
18
+ export interface ITestResult {
19
+ // Test / tool identifiers
20
+ fullId: string;
21
+ toolName: string;
22
+ description: string;
23
+
24
+ // Tool invocation parameters
25
+ parameters: unknown | null;
26
+
27
+ // Temporal metadata
28
+ timestamp: string; // ISO string
29
+ duration: number; // milliseconds
30
+
31
+ // Execution status
32
+ status: 'pending' | 'passed' | 'failed' | 'skipped' | 'expected_failure';
33
+
34
+ // Marker icon for logs (may be absent in "pending")
35
+ marker?: string;
36
+
37
+ // MCP response
38
+ response: unknown | null;
39
+
40
+ // Error (human-readable message)
41
+ error: string | null;
42
+
43
+ // Additional error details (structured)
44
+ errorDetails?: unknown | null;
45
+
46
+ // Full MCP response on error (JSON-RPC response)
47
+ fullMcpResponse?: unknown;
48
+
49
+ // Request headers used for the MCP server call
50
+ requestHeaders?: Record<string, string>;
51
+ }
52
+
53
+ /**
54
+ * Format test result as Markdown
55
+ */
56
+ export const formatResultAsMarkdown = (result: ITestResult) => {
57
+ const t = '```';
58
+ const mdText = (s: string | null) => `${t}\n${s}\n${t}`;
59
+ const mdDescr = (s: string) => `${t}description\n${s}\n${t}`;
60
+ const mdJson = (v: any) => `${t}json\n${v && JSON.stringify(v, null, 2)}\n${t}`;
61
+
62
+ let resultStatus = '⚠️ RESULT STATUS UNKNOWN';
63
+ let errorText = '';
64
+ // md += `## Response\n\n\`\`\`json\n${JSON.stringify(result.response, null, 2)}\n\`\`\`\n\n`;
65
+
66
+ if (result.status === 'passed') {
67
+ resultStatus = '✅ PASSED';
68
+ } else {
69
+ // Show full MCP response as seen by the agent, or fallback to separate sections
70
+ if (result.fullMcpResponse) {
71
+ errorText = `## MCP Response (as seen by agent)\n\n${mdJson(result.fullMcpResponse)}\n\n`;
72
+ } else {
73
+ errorText = `## Error\n\n${mdText(result.error)}\n\n`;
74
+ // Add detailed error information if available
75
+ if (result.errorDetails) {
76
+ errorText += `## Error Details\n\n${mdJson(result.errorDetails)}\n\n`;
77
+ }
78
+ }
79
+ if (result.status === 'expected_failure') {
80
+ resultStatus = '⚠️ Expected failure - test validation successful';
81
+ } else {
82
+ resultStatus = '❌ FAILED';
83
+ }
84
+ }
85
+
86
+ let requestHeaders = '';
87
+ if (result.requestHeaders && Object.keys(result.requestHeaders).length > 0) {
88
+ requestHeaders = `\nHeaders:\n${Object.entries(result.requestHeaders).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n`;
89
+ }
90
+
91
+ // Format response section
92
+ let responseText = '';
93
+ if (result.response !== null && result.response !== undefined) {
94
+ try {
95
+ let parsedResponse: any = result.response;
96
+ let isJsonParsed = false;
97
+
98
+ // If response is a string, try to parse as JSON first
99
+ if (typeof result.response === 'string') {
100
+ try {
101
+ parsedResponse = JSON.parse(result.response);
102
+ isJsonParsed = true;
103
+ } catch {
104
+ // If not valid JSON, treat as text
105
+ responseText = `## Response\n\n${mdText(result.response)}\n\n`;
106
+ }
107
+ } else if (typeof result.response === 'object') {
108
+ isJsonParsed = true;
109
+ }
110
+
111
+ // If we have a successfully parsed or original object
112
+ if (isJsonParsed && typeof parsedResponse === 'object') {
113
+ let text = parsedResponse;
114
+ let addText = '';
115
+ // Check if response has content[0].text structure and extract text
116
+ if (Array.isArray(parsedResponse?.content) && parsedResponse.content[0]?.text) {
117
+ const textContent = parsedResponse.content[0].text;
118
+ parsedResponse.content[0].text = '📋';
119
+ text = parsedResponse;
120
+ addText = `## Formatted Text 📋\n${mdText(textContent)}\n\n`;
121
+ }
122
+ responseText = `## Response\n\n${mdJson(text)}\n\n${addText}`;
123
+ }
124
+
125
+ } catch {
126
+ // Fallback to text if any parsing errors
127
+ responseText = `## Response\n\n${mdText(String(result.response))}\n\n`;
128
+ }
129
+ }
130
+
131
+ return `${resultStatus} / ${result.timestamp} / ${result.duration}ms
132
+ # ${result.toolName}
133
+ ${requestHeaders}
134
+ ${mdDescr(result.description)}
135
+
136
+ parameters:
137
+ ${mdJson(result.parameters)}
138
+
139
+ ${responseText}${errorText}`;
140
+ };
141
+
142
+ /**
143
+ * Log test result to individual file
144
+ */
145
+ export const logResultToFile = async (result: ITestResult) => {
146
+ // const filename = `${result.fullId}_${result.toolName}.md`;
147
+ const filename = `${result.toolName}.md`;
148
+ const filepath = path.join(RESULTS_DIR, filename);
149
+
150
+ const content = formatResultAsMarkdown(result);
151
+
152
+ try {
153
+ await fsp.writeFile(filepath, content, 'utf-8');
154
+ } catch (error: Error | any) {
155
+ console.log(chalk.red(` Failed to write log file: ${error.message}`));
156
+ }
157
+ };