@perfai/mcp 1.0.24

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +352 -0
  3. package/dist/auth/authManager.d.ts +83 -0
  4. package/dist/auth/authManager.d.ts.map +1 -0
  5. package/dist/auth/authManager.js +555 -0
  6. package/dist/auth/sessionCache.d.ts +5 -0
  7. package/dist/auth/sessionCache.d.ts.map +1 -0
  8. package/dist/auth/sessionCache.js +29 -0
  9. package/dist/auth/sessionStorage.d.ts +53 -0
  10. package/dist/auth/sessionStorage.d.ts.map +1 -0
  11. package/dist/auth/sessionStorage.js +234 -0
  12. package/dist/auth/types.d.ts +28 -0
  13. package/dist/auth/types.d.ts.map +1 -0
  14. package/dist/auth/types.js +1 -0
  15. package/dist/config.d.ts +65 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +74 -0
  18. package/dist/server.d.ts +3 -0
  19. package/dist/server.d.ts.map +1 -0
  20. package/dist/server.js +144 -0
  21. package/dist/setup-config.d.ts +4 -0
  22. package/dist/setup-config.d.ts.map +1 -0
  23. package/dist/setup-config.js +69 -0
  24. package/dist/tools/index.d.ts +361 -0
  25. package/dist/tools/index.d.ts.map +1 -0
  26. package/dist/tools/index.js +275 -0
  27. package/dist/tools/protected/aiFixDesignIssue.d.ts +17 -0
  28. package/dist/tools/protected/aiFixDesignIssue.d.ts.map +1 -0
  29. package/dist/tools/protected/aiFixDesignIssue.js +205 -0
  30. package/dist/tools/protected/aiFixQualityIssue.d.ts +17 -0
  31. package/dist/tools/protected/aiFixQualityIssue.d.ts.map +1 -0
  32. package/dist/tools/protected/aiFixQualityIssue.js +188 -0
  33. package/dist/tools/protected/aiFixSecurityIssue.d.ts +17 -0
  34. package/dist/tools/protected/aiFixSecurityIssue.d.ts.map +1 -0
  35. package/dist/tools/protected/aiFixSecurityIssue.js +205 -0
  36. package/dist/tools/protected/checkDesignFixes.d.ts +17 -0
  37. package/dist/tools/protected/checkDesignFixes.d.ts.map +1 -0
  38. package/dist/tools/protected/checkDesignFixes.js +199 -0
  39. package/dist/tools/protected/checkQualityFixes.d.ts +17 -0
  40. package/dist/tools/protected/checkQualityFixes.d.ts.map +1 -0
  41. package/dist/tools/protected/checkQualityFixes.js +199 -0
  42. package/dist/tools/protected/checkSecurityFixes.d.ts +17 -0
  43. package/dist/tools/protected/checkSecurityFixes.d.ts.map +1 -0
  44. package/dist/tools/protected/checkSecurityFixes.js +177 -0
  45. package/dist/tools/protected/listApis.d.ts +28 -0
  46. package/dist/tools/protected/listApis.d.ts.map +1 -0
  47. package/dist/tools/protected/listApis.js +102 -0
  48. package/dist/tools/protected/logout.d.ts +11 -0
  49. package/dist/tools/protected/logout.d.ts.map +1 -0
  50. package/dist/tools/protected/logout.js +22 -0
  51. package/dist/tools/protected/manageOrganizations.d.ts +26 -0
  52. package/dist/tools/protected/manageOrganizations.d.ts.map +1 -0
  53. package/dist/tools/protected/manageOrganizations.js +147 -0
  54. package/dist/tools/protected/runDesignTest.d.ts +21 -0
  55. package/dist/tools/protected/runDesignTest.d.ts.map +1 -0
  56. package/dist/tools/protected/runDesignTest.js +132 -0
  57. package/dist/tools/protected/runQualityTest.d.ts +21 -0
  58. package/dist/tools/protected/runQualityTest.d.ts.map +1 -0
  59. package/dist/tools/protected/runQualityTest.js +150 -0
  60. package/dist/tools/protected/runSecurityTest.d.ts +21 -0
  61. package/dist/tools/protected/runSecurityTest.d.ts.map +1 -0
  62. package/dist/tools/protected/runSecurityTest.js +107 -0
  63. package/dist/tools/protected/selectApi.d.ts +24 -0
  64. package/dist/tools/protected/selectApi.d.ts.map +1 -0
  65. package/dist/tools/protected/selectApi.js +172 -0
  66. package/dist/tools/protected/setup.d.ts +11 -0
  67. package/dist/tools/protected/setup.d.ts.map +1 -0
  68. package/dist/tools/protected/setup.js +151 -0
  69. package/dist/tools/protected/showDesignIssues.d.ts +38 -0
  70. package/dist/tools/protected/showDesignIssues.d.ts.map +1 -0
  71. package/dist/tools/protected/showDesignIssues.js +201 -0
  72. package/dist/tools/protected/showFixedIssues.d.ts +11 -0
  73. package/dist/tools/protected/showFixedIssues.d.ts.map +1 -0
  74. package/dist/tools/protected/showFixedIssues.js +36 -0
  75. package/dist/tools/protected/showQualityIssues.d.ts +33 -0
  76. package/dist/tools/protected/showQualityIssues.d.ts.map +1 -0
  77. package/dist/tools/protected/showQualityIssues.js +225 -0
  78. package/dist/tools/protected/showSecurityIssues.d.ts +47 -0
  79. package/dist/tools/protected/showSecurityIssues.d.ts.map +1 -0
  80. package/dist/tools/protected/showSecurityIssues.js +212 -0
  81. package/dist/tools/protected/summarizeIssues.d.ts +11 -0
  82. package/dist/tools/protected/summarizeIssues.d.ts.map +1 -0
  83. package/dist/tools/protected/summarizeIssues.js +161 -0
  84. package/dist/tools/protected/userInfo.d.ts +11 -0
  85. package/dist/tools/protected/userInfo.d.ts.map +1 -0
  86. package/dist/tools/protected/userInfo.js +21 -0
  87. package/dist/tools/protected/visionAiAppLearning.d.ts +37 -0
  88. package/dist/tools/protected/visionAiAppLearning.d.ts.map +1 -0
  89. package/dist/tools/protected/visionAiAppLearning.js +122 -0
  90. package/dist/tools/public/authStatus.d.ts +11 -0
  91. package/dist/tools/public/authStatus.d.ts.map +1 -0
  92. package/dist/tools/public/authStatus.js +78 -0
  93. package/dist/tools/public/login.d.ts +12 -0
  94. package/dist/tools/public/login.d.ts.map +1 -0
  95. package/dist/tools/public/login.js +230 -0
  96. package/dist/types/api.d.ts +12 -0
  97. package/dist/types/api.d.ts.map +1 -0
  98. package/dist/types/api.js +1 -0
  99. package/dist/utils/dockerRunner.d.ts +44 -0
  100. package/dist/utils/dockerRunner.d.ts.map +1 -0
  101. package/dist/utils/dockerRunner.js +300 -0
  102. package/dist/utils/formatters.d.ts +14 -0
  103. package/dist/utils/formatters.d.ts.map +1 -0
  104. package/dist/utils/formatters.js +510 -0
  105. package/dist/utils/promptBuilder.d.ts +4 -0
  106. package/dist/utils/promptBuilder.d.ts.map +1 -0
  107. package/dist/utils/promptBuilder.js +132 -0
  108. package/package.json +67 -0
@@ -0,0 +1,212 @@
1
+ import axios from "axios";
2
+ import { API_ENDPOINTS } from "../../config.js";
3
+ import { formatSecurityIssues } from "../../utils/formatters.js";
4
+ export const showSecurityIssuesTool = {
5
+ name: "show_security_issues",
6
+ description: "๐Ÿ”’ List security issues for selected APP โ€” filter by severity, status, and sort order (shows 20 at a time)",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ limit: {
11
+ type: "number",
12
+ description: "Number of issues to fetch (default: 20, max: 100)"
13
+ },
14
+ severities: {
15
+ type: "array",
16
+ items: { type: "string", enum: ["Low", "Medium", "High", "Critical"] },
17
+ description: "Filter by one or more severity levels e.g. [\"Critical\", \"High\"]"
18
+ },
19
+ status: {
20
+ type: "string",
21
+ enum: ["Open", "Dismissed"],
22
+ description: "Filter by issue status (default: Open)"
23
+ },
24
+ sort_by: {
25
+ type: "string",
26
+ enum: ["severity", "cvss", "method", "path", "type"],
27
+ description: "Field to sort by (default: severity)"
28
+ },
29
+ sort_order: {
30
+ type: "string",
31
+ enum: ["ASC", "DESC"],
32
+ description: "Sort direction (default: DESC โ€” highest severity first)"
33
+ },
34
+ search: {
35
+ type: "string",
36
+ description: "Search issues by keyword (optional)"
37
+ },
38
+ cursor: {
39
+ type: "string",
40
+ description: "Pagination cursor for fetching next page (optional)"
41
+ }
42
+ },
43
+ additionalProperties: false,
44
+ },
45
+ };
46
+ export async function handleShowSecurityIssues(authManager, args) {
47
+ try {
48
+ const requestedLimit = args.limit;
49
+ const search = args.search?.trim();
50
+ const cursor = args.cursor;
51
+ const severities = args.severities || [];
52
+ const status = args.status || 'Open';
53
+ const sortBy = args.sort_by || 'severity';
54
+ const sortOrder = args.sort_order || 'DESC';
55
+ // Default to 20 issues if no limit specified
56
+ const limit = requestedLimit ? Math.min(requestedLimit, 100) : 20; // Default 20, max 100 per request
57
+ // Make authenticated request
58
+ const userInfo = authManager.getUserInfo();
59
+ const accessToken = authManager.getAccessToken();
60
+ const tokenType = authManager.getTokenType();
61
+ const orgId = authManager.getSelectedOrgId();
62
+ if (!accessToken) {
63
+ throw new Error('No access token available');
64
+ }
65
+ if (!orgId) {
66
+ // Debug info for user
67
+ const orgs = authManager.getOrganizations();
68
+ const sessionInfo = authManager.session ? `Session exists with ${Object.keys(authManager.session).join(', ')}` : 'No session';
69
+ throw new Error(`No organization selected. Debug: ${sessionInfo}. Organizations: ${orgs.length}. Selected: ${authManager.session?.selectedOrgId || 'none'}`);
70
+ }
71
+ // Always require selected APP
72
+ const selectedAppId = authManager.getSelectedApiId();
73
+ const selectedAppData = authManager.getSelectedApiData();
74
+ const selectedAppIdentifier = authManager.getSelectedApiIdentifier();
75
+ if (!selectedAppId) {
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text",
80
+ text: `โŒ **No APP Selected**\n\n๐ŸŽฏ **Required**: You must select an APP first to view its security issues.\n\n๐Ÿ“‹ **Steps**:\n1. Run \`list_apps\` to see available APPs\n2. Run \`select_app\` with an APP ID\n3. Then run this tool again`,
81
+ },
82
+ ],
83
+ };
84
+ }
85
+ // First, get the app details using the catalog ID to get the security_privacy_test_appId
86
+ const catalogUrl = `${API_ENDPOINTS.API_CATALOG_SERVICE_IDS}/${selectedAppId}`;
87
+ let appDetails;
88
+ try {
89
+ const catalogResponse = await axios.get(catalogUrl, {
90
+ headers: {
91
+ 'Authorization': `${tokenType} ${accessToken}`,
92
+ 'Content-Type': 'application/json',
93
+ 'X-Org-ID': orgId
94
+ },
95
+ timeout: 30000
96
+ });
97
+ if (catalogResponse.status !== 200 || !catalogResponse.data) {
98
+ throw new Error(`Failed to fetch app details with status ${catalogResponse.status}`);
99
+ }
100
+ appDetails = catalogResponse.data;
101
+ if (!appDetails.security_privacy_test_appId) {
102
+ throw new Error('No security_privacy_test_appId found for this app');
103
+ }
104
+ }
105
+ catch (error) {
106
+ const errorMessage = error instanceof Error ? error.message : String(error);
107
+ return {
108
+ content: [
109
+ {
110
+ type: "text",
111
+ text: `โŒ **Error fetching app details**\n\n๐Ÿ”ด **Error**: ${errorMessage}\n\n๐Ÿ’ก **Tip**: Make sure the selected app has security testing enabled.`,
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ // Use the security_privacy_test_appId for fetching security issues
117
+ const securityAppId = appDetails.security_privacy_test_appId;
118
+ // Store the security app ID for use in other tools (like runSecurityTest)
119
+ authManager.setSelectedSecurityAppId(securityAppId);
120
+ // Build query with all supported API params
121
+ const params = new URLSearchParams();
122
+ params.set('app_id', securityAppId);
123
+ params.set('limit', String(limit));
124
+ params.set('sortBy', sortBy);
125
+ params.set('sortOrder', sortOrder);
126
+ if (cursor)
127
+ params.set('cursor', cursor);
128
+ if (search)
129
+ params.set('search', search);
130
+ severities.forEach((s) => params.append('severities[]', s));
131
+ if (status === 'Dismissed')
132
+ params.set('isDismissed', 'true');
133
+ const issuesUrl = `${API_ENDPOINTS.SECURITY_ISSUES_BY_APP}?${params.toString()}`;
134
+ console.error('๐Ÿ” Debug: Fetching security issues');
135
+ const authHeaders = {
136
+ 'Authorization': `${tokenType} ${accessToken}`,
137
+ 'Content-Type': 'application/json',
138
+ 'X-Org-ID': orgId
139
+ };
140
+ let response;
141
+ try {
142
+ response = await axios.get(issuesUrl, { headers: authHeaders, timeout: 30000 });
143
+ }
144
+ catch (err) {
145
+ if (cursor && err?.response?.status === 500) {
146
+ const retryParams = new URLSearchParams(params);
147
+ retryParams.delete('cursor');
148
+ response = await axios.get(`${API_ENDPOINTS.SECURITY_ISSUES_BY_APP}?${retryParams.toString()}`, { headers: authHeaders, timeout: 30000 });
149
+ }
150
+ else {
151
+ throw err;
152
+ }
153
+ }
154
+ if (response.status !== 200 || !response.data) {
155
+ throw new Error(`Request failed with status ${response.status}`);
156
+ }
157
+ const issues = response.data.data || [];
158
+ const allIssues = issues;
159
+ let currentCursor = response.data.nextCursor || null;
160
+ let hasMore = response.data.hasMore || false;
161
+ if (issues.length < limit) {
162
+ hasMore = false;
163
+ currentCursor = null;
164
+ }
165
+ const appDisplayName = selectedAppData?.label || selectedAppData?.api_name || appDetails?.api_name || selectedAppId || '';
166
+ const moreInfo = hasMore ? ' (more available)' : '';
167
+ const selectedOrg = authManager.getOrganizations().find((org) => org.org_id === orgId);
168
+ const orgName = selectedOrg?.orgDetails?.name || orgId || '';
169
+ const activeFilters = [];
170
+ if (severities.length)
171
+ activeFilters.push(`severities: ${severities.join(', ')}`);
172
+ if (status !== 'Open')
173
+ activeFilters.push(`status: ${status}`);
174
+ if (search)
175
+ activeFilters.push(`search: "${search}"`);
176
+ activeFilters.push(`sort: ${sortBy} ${sortOrder}`);
177
+ const filterInfo = `๐ŸŽฏ **APP**: ${appDisplayName} | ๐Ÿข **Org**: ${orgName}\n` +
178
+ `๐Ÿ”ฝ **Filters**: ${activeFilters.join(' | ')}\n` +
179
+ `๐Ÿ“Š **Issues shown**: ${allIssues.length}${moreInfo}\n\n`;
180
+ const formattedText = formatSecurityIssues(allIssues, userInfo, search, hasMore, currentCursor);
181
+ return {
182
+ content: [
183
+ {
184
+ type: "text",
185
+ text: filterInfo + formattedText,
186
+ },
187
+ ],
188
+ };
189
+ }
190
+ catch (error) {
191
+ const errorMessage = error instanceof Error ? error.message : String(error);
192
+ // Check if it's a 500 error specifically
193
+ if (errorMessage.includes('500')) {
194
+ return {
195
+ content: [
196
+ {
197
+ type: "text",
198
+ text: `โŒ **Server Error (500)**\n\n๐Ÿ”ด **Error**: ${errorMessage}\n\n๐Ÿ’ก **Possible Causes**:\nโ€ข Invalid pagination cursor\nโ€ข Server temporarily unavailable\nโ€ข Rate limiting\n\n๐Ÿ”„ **Try**:\n1. Run \`show_security_issues\` without cursor to get fresh results\n2. Wait a few minutes and try again\n3. Check if your authentication is still valid`,
199
+ },
200
+ ],
201
+ };
202
+ }
203
+ return {
204
+ content: [
205
+ {
206
+ type: "text",
207
+ text: `โŒ **Error fetching security issues**\n\n๐Ÿ”ด **Error**: ${errorMessage}\n\n๐Ÿ’ก **Tip**: Make sure you're authenticated and have access to security data.\n\n๐Ÿ”„ **Try**: Re-running the 'login' tool if the error persists.`,
208
+ },
209
+ ],
210
+ };
211
+ }
212
+ }
@@ -0,0 +1,11 @@
1
+ export declare const summarizeIssuesTool: {
2
+ readonly name: "summarize_issues";
3
+ readonly description: "๐Ÿ“Š Get a full summary dashboard for the selected APP โ€” security risk, design issues, quality issues, attack surface, bug bounty savings, and coverage (requires authentication)";
4
+ readonly inputSchema: {
5
+ readonly type: "object";
6
+ readonly properties: {};
7
+ readonly additionalProperties: false;
8
+ };
9
+ };
10
+ export declare function handleSummarizeIssues(authManager: any): Promise<any>;
11
+ //# sourceMappingURL=summarizeIssues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarizeIssues.d.ts","sourceRoot":"","sources":["../../../src/tools/protected/summarizeIssues.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB;;;;;;;;CAQtB,CAAC;AAcX,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CA6I1E"}
@@ -0,0 +1,161 @@
1
+ import axios from "axios";
2
+ import { API_ENDPOINTS } from "../../config.js";
3
+ export const summarizeIssuesTool = {
4
+ name: "summarize_issues",
5
+ description: "๐Ÿ“Š Get a full summary dashboard for the selected APP โ€” security risk, design issues, quality issues, attack surface, bug bounty savings, and coverage (requires authentication)",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {},
9
+ additionalProperties: false,
10
+ },
11
+ };
12
+ function fmt(n, prefix = '') {
13
+ if (n === undefined || n === null)
14
+ return 'N/A';
15
+ return prefix + n.toLocaleString();
16
+ }
17
+ function fmtMoney(n) {
18
+ if (!n)
19
+ return '$0';
20
+ if (n >= 1_000_000)
21
+ return `$${(n / 1_000_000).toFixed(1)}M`;
22
+ if (n >= 1_000)
23
+ return `$${(n / 1_000).toFixed(1)}K`;
24
+ return `$${n}`;
25
+ }
26
+ export async function handleSummarizeIssues(authManager) {
27
+ try {
28
+ const accessToken = authManager.getAccessToken();
29
+ const tokenType = authManager.getTokenType();
30
+ const orgId = authManager.getSelectedOrgId();
31
+ if (!accessToken)
32
+ throw new Error('No access token available');
33
+ if (!orgId)
34
+ throw new Error('No organization selected. Use manage_organizations first.');
35
+ const selectedAppId = authManager.getSelectedApiId();
36
+ const selectedAppData = authManager.getSelectedApiData();
37
+ if (!selectedAppId) {
38
+ return {
39
+ content: [{
40
+ type: "text",
41
+ text: `โŒ **No APP Selected**\n\n๐ŸŽฏ Select an APP first.\n\n๐Ÿ“‹ Steps:\n1. Run \`list_apps\`\n2. Run \`select_app\`\n3. Then run \`summarize_issues\``,
42
+ }],
43
+ };
44
+ }
45
+ const url = `${API_ENDPOINTS.APP_SUMMARY}/${selectedAppId}`;
46
+ const authHeaders = {
47
+ 'Authorization': `${tokenType} ${accessToken}`,
48
+ 'Content-Type': 'application/json',
49
+ 'X-Org-ID': orgId,
50
+ };
51
+ const response = await axios.get(url, { headers: authHeaders, timeout: 30000 });
52
+ if (response.status !== 200 || !response.data) {
53
+ throw new Error(`Summary request failed with status ${response.status}`);
54
+ }
55
+ const d = response.data;
56
+ const appName = selectedAppData?.label || d.latest_run?.api_name || selectedAppId;
57
+ const selectedOrg = authManager.getOrganizations().find((org) => org.org_id === orgId);
58
+ const orgName = selectedOrg?.orgDetails?.name || orgId;
59
+ const sec = d.security_summary || {};
60
+ const des = d.design_summary || {};
61
+ const qua = d.spec_validation_summary || {};
62
+ const run = d.latest_run || {};
63
+ const bug = d.bugBounty?.active || {};
64
+ const lines = [];
65
+ // Header
66
+ lines.push(`๐Ÿ“Š **${appName}** โ€” Issue Summary`);
67
+ lines.push(`๐Ÿข **Org**: ${orgName} | ๐Ÿ• **Last run**: ${run.run_dt ? new Date(run.run_dt).toLocaleString() : 'N/A'} | ๐Ÿ” **Run #${run.run_number ?? 'N/A'}**`);
68
+ lines.push(`๐Ÿ“‹ **Status**: ${d.registrationStatus || 'Unknown'} | ๐Ÿ“… **Schedule**: ${d.scheduleType || 'N/A'} | ๐ŸŽฏ **Coverage**: ${d.final_coverage !== undefined ? d.final_coverage + '%' : 'N/A'}`);
69
+ // โ”€โ”€ Security Risk โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
70
+ lines.push(`\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
71
+ lines.push(`๐Ÿ”’ **SECURITY RISK**`);
72
+ lines.push(`โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
73
+ const secCritHigh = (sec.total_critical || 0) + (sec.total_high || 0);
74
+ const secMedLow = (sec.total_medium || 0) + (sec.total_low || 0);
75
+ lines.push(` ๐Ÿ”ด Critical/High : ${secCritHigh} ${secCritHigh > 0 ? 'โ† Immediate action required' : 'โœ… None'}`);
76
+ lines.push(` ๐ŸŸก Medium/Low : ${secMedLow}`);
77
+ lines.push(` โœ… Fixed : ${fmt(sec.total_fixed_issues)}`);
78
+ lines.push(` ๐Ÿšซ Dismissed : ${fmt(sec.total_dismissed_issues)}`);
79
+ lines.push(` ๐Ÿ“Š Total : ${fmt(sec.total_issues)}`);
80
+ // Bug bounty
81
+ const totalSavings = bug.totalSavings || 0;
82
+ if (totalSavings > 0) {
83
+ const bySev = bug.savingsBySeverity || {};
84
+ lines.push(`\n ๐Ÿ’ฐ **Bug Bounty Savings**: ${fmtMoney(totalSavings)} potential costs avoided`);
85
+ if (bySev.Critical)
86
+ lines.push(` Critical: ${fmtMoney(bySev.Critical)}`);
87
+ if (bySev.High)
88
+ lines.push(` High: ${fmtMoney(bySev.High)}`);
89
+ if (bySev.Medium)
90
+ lines.push(` Medium: ${fmtMoney(bySev.Medium)}`);
91
+ }
92
+ // โ”€โ”€ Design Issues โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
93
+ lines.push(`\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
94
+ lines.push(`๐ŸŽจ **APPLICATION DESIGN ISSUES**`);
95
+ lines.push(`โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
96
+ lines.push(` ๐Ÿ”ด Critical : ${fmt(des.total_critical)}`);
97
+ lines.push(` ๐ŸŸ  High : ${fmt(des.total_high)}`);
98
+ lines.push(` ๐ŸŸก Medium : ${fmt(des.total_medium)}`);
99
+ lines.push(` ๐Ÿ“Š Total : ${fmt(des.total_issues)}`);
100
+ // โ”€โ”€ Quality / Spec Validation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
101
+ lines.push(`\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
102
+ lines.push(`๐Ÿ” **QUALITY / SPEC VALIDATION**`);
103
+ lines.push(`โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
104
+ if ((qua.total_issues || 0) === 0) {
105
+ lines.push(` โœ… No quality issues โ€” specifications look good`);
106
+ }
107
+ else {
108
+ lines.push(` ๐Ÿšจ Blocker : ${fmt(qua.total_blocker)}`);
109
+ lines.push(` ๐Ÿ”ด Critical : ${fmt(qua.total_critical)}`);
110
+ lines.push(` ๐ŸŸ  High : ${fmt(qua.total_high)}`);
111
+ lines.push(` ๐ŸŸก Medium : ${fmt(qua.total_medium)}`);
112
+ lines.push(` ๐Ÿ“Š Total : ${fmt(qua.total_issues)}`);
113
+ }
114
+ // โ”€โ”€ Attack Surface โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
115
+ lines.push(`\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
116
+ lines.push(`๐Ÿ—บ๏ธ **ATTACK SURFACE**`);
117
+ lines.push(`โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
118
+ lines.push(` ๐Ÿ“ก Endpoints : ${fmt(run.total_endpoints)}`);
119
+ lines.push(` ๐Ÿ‘ค Roles tested : ${fmt(d.unique_privileges?.length)}${d.unique_privileges?.length ? ` (${d.unique_privileges.join(', ')})` : ''}`);
120
+ lines.push(` ๐Ÿ”ฌ Security tests : ${fmt(d.testDataSecurityCount)}`);
121
+ lines.push(` ๐Ÿ”’ Sensitive data types : ${fmt(d.total_pii)}`);
122
+ if (run.total_endpoints) {
123
+ lines.push(`\n HTTP Methods:`);
124
+ if (run.total_get)
125
+ lines.push(` GET : ${run.total_get}`);
126
+ if (run.total_post)
127
+ lines.push(` POST : ${run.total_post}`);
128
+ if (run.total_put)
129
+ lines.push(` PUT : ${run.total_put}`);
130
+ if (run.total_patch)
131
+ lines.push(` PATCH : ${run.total_patch}`);
132
+ if (run.total_delete)
133
+ lines.push(` DELETE : ${run.total_delete}`);
134
+ }
135
+ // โ”€โ”€ Version โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
136
+ if (d.semVer || d.calVer) {
137
+ lines.push(`\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
138
+ lines.push(`๐Ÿ“ฆ **VERSION**`);
139
+ lines.push(`โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`);
140
+ if (d.semVer)
141
+ lines.push(` SemVer : ${d.semVer}`);
142
+ if (d.calVer)
143
+ lines.push(` CalVer : ${d.calVer}`);
144
+ if (d.total_releases !== undefined)
145
+ lines.push(` Releases: ${d.total_releases}`);
146
+ }
147
+ lines.push(`\n๐Ÿ’ก Use \`show_security_issues\`, \`show_design_issues\`, or \`show_quality_issues\` to see individual issues.`);
148
+ return {
149
+ content: [{ type: "text", text: lines.join('\n') }],
150
+ };
151
+ }
152
+ catch (error) {
153
+ const errorMessage = error instanceof Error ? error.message : String(error);
154
+ return {
155
+ content: [{
156
+ type: "text",
157
+ text: `โŒ **Error fetching issue summary**\n\n๐Ÿ”ด **Error**: ${errorMessage}\n\n๐Ÿ’ก Make sure you're authenticated and have an APP selected.`,
158
+ }],
159
+ };
160
+ }
161
+ }
@@ -0,0 +1,11 @@
1
+ export declare const userInfoTool: {
2
+ readonly name: "user_info";
3
+ readonly description: "๐Ÿ‘ค Get detailed information about the authenticated user";
4
+ readonly inputSchema: {
5
+ readonly type: "object";
6
+ readonly properties: {};
7
+ readonly additionalProperties: false;
8
+ };
9
+ };
10
+ export declare function handleUserInfo(authManager: any): Promise<any>;
11
+ //# sourceMappingURL=userInfo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"userInfo.d.ts","sourceRoot":"","sources":["../../../src/tools/protected/userInfo.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY;;;;;;;;CAQf,CAAC;AAEX,wBAAsB,cAAc,CAAC,WAAW,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAYnE"}
@@ -0,0 +1,21 @@
1
+ export const userInfoTool = {
2
+ name: "user_info",
3
+ description: "๐Ÿ‘ค Get detailed information about the authenticated user",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {},
7
+ additionalProperties: false,
8
+ },
9
+ };
10
+ export async function handleUserInfo(authManager) {
11
+ const userInfo = authManager.getUserInfo();
12
+ const accessToken = authManager.getAccessToken();
13
+ return {
14
+ content: [
15
+ {
16
+ type: "text",
17
+ text: `๐Ÿ‘ค **User Information**\n\n**Name:** ${userInfo.name || 'Not provided'}\n**Email:** ${userInfo.email}\n**User ID:** ${userInfo.sub}\n**Picture:** ${userInfo.picture || 'Not provided'}\n**Email Verified:** ${userInfo.email_verified ? 'โœ… Yes' : 'โŒ No'}\n**Locale:** ${userInfo.locale || 'Not provided'}\n**Last Updated:** ${userInfo.updated_at || 'Not provided'}\n\n๐Ÿ”‘ **Access Token:** ${accessToken ? `${accessToken.substring(0, 20)}...` : 'Not available'}`,
18
+ },
19
+ ],
20
+ };
21
+ }
@@ -0,0 +1,37 @@
1
+ export declare const visionAiAppLearningTool: {
2
+ readonly name: "vision_ai_app_learning";
3
+ readonly description: "๐Ÿ“„ Run Vision AI App Learning using Docker (GENERATE_SPEC). Requires authentication and an app selected. Triggers the vision app learning image in LOCAL mode.";
4
+ readonly inputSchema: {
5
+ readonly type: "object";
6
+ readonly properties: {
7
+ readonly app_url: {
8
+ readonly type: "string";
9
+ readonly description: "URL of the app to explore for OAS generation (e.g. https://myapp.example.com, http://localhost:3000, or http://0.0.0.0:3000). For Docker, localhost and 0.0.0.0 are rewritten to host.docker.internal so the container can reach your host.";
10
+ };
11
+ readonly spec_url: {
12
+ readonly type: "string";
13
+ readonly description: "Optional existing spec URL to extend or validate";
14
+ };
15
+ readonly chat_id: {
16
+ readonly type: "string";
17
+ readonly description: "Optional chat ID for linking to a conversation";
18
+ };
19
+ readonly model_config: {
20
+ readonly type: "string";
21
+ readonly description: "Optional model config (default: Advance)";
22
+ };
23
+ readonly account_email: {
24
+ readonly type: "string";
25
+ readonly description: "Optional email for the app under test (pre-existing account login)";
26
+ };
27
+ readonly account_password: {
28
+ readonly type: "string";
29
+ readonly description: "Optional password for the app under test (pre-existing account login)";
30
+ };
31
+ };
32
+ readonly required: readonly ["app_url"];
33
+ readonly additionalProperties: true;
34
+ };
35
+ };
36
+ export declare function handleVisionAiAppLearning(authManager: any, args: any): Promise<any>;
37
+ //# sourceMappingURL=visionAiAppLearning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visionAiAppLearning.d.ts","sourceRoot":"","sources":["../../../src/tools/protected/visionAiAppLearning.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmC1B,CAAC;AAEX,wBAAsB,yBAAyB,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAgGzF"}
@@ -0,0 +1,122 @@
1
+ import { runDockerVisionAiAppLearning } from "../../utils/dockerRunner.js";
2
+ export const visionAiAppLearningTool = {
3
+ name: "vision_ai_app_learning",
4
+ description: "๐Ÿ“„ Run Vision AI App Learning using Docker (GENERATE_SPEC). Requires authentication and an app selected. Triggers the vision app learning image in LOCAL mode.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ app_url: {
9
+ type: "string",
10
+ description: "URL of the app to explore for OAS generation (e.g. https://myapp.example.com, http://localhost:3000, or http://0.0.0.0:3000). For Docker, localhost and 0.0.0.0 are rewritten to host.docker.internal so the container can reach your host.",
11
+ },
12
+ spec_url: {
13
+ type: "string",
14
+ description: "Optional existing spec URL to extend or validate",
15
+ },
16
+ chat_id: {
17
+ type: "string",
18
+ description: "Optional chat ID for linking to a conversation",
19
+ },
20
+ model_config: {
21
+ type: "string",
22
+ description: "Optional model config (default: Advance)",
23
+ },
24
+ account_email: {
25
+ type: "string",
26
+ description: "Optional email for the app under test (pre-existing account login)",
27
+ },
28
+ account_password: {
29
+ type: "string",
30
+ description: "Optional password for the app under test (pre-existing account login)",
31
+ },
32
+ },
33
+ required: ["app_url"],
34
+ additionalProperties: true,
35
+ },
36
+ };
37
+ export async function handleVisionAiAppLearning(authManager, args) {
38
+ try {
39
+ const appUrl = args.app_url;
40
+ const specUrl = args.spec_url;
41
+ const chatId = args.chat_id;
42
+ const modelConfig = args.model_config;
43
+ const accountEmail = args.account_email;
44
+ const accountPassword = args.account_password;
45
+ const accessToken = authManager.getAccessToken();
46
+ const orgId = authManager.getSelectedOrgId();
47
+ const selectedApiId = authManager.getSelectedApiId();
48
+ const userInfo = authManager.getUserInfo();
49
+ const userId = userInfo?.sub ?? undefined;
50
+ if (!accessToken) {
51
+ throw new Error("No access token available");
52
+ }
53
+ if (!orgId) {
54
+ throw new Error("No organization selected. Please select an organization first using manage_organizations tool.");
55
+ }
56
+ if (!selectedApiId) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: `โŒ **No App Selected**\n\n๐ŸŽฏ **Required**: You must select an app first to run Vision AI App Learning.\n\n๐Ÿ“‹ **Steps**:\n1. Run \`list_apps\` to see available apps\n2. Run \`select_app\` with an app ID\n3. Then run this tool again with \`app_url\` (the URL of your app to explore)`,
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ if (!appUrl) {
67
+ throw new Error("app_url is required");
68
+ }
69
+ try {
70
+ new URL(appUrl);
71
+ }
72
+ catch {
73
+ throw new Error("Invalid URL format for app_url");
74
+ }
75
+ const result = await runDockerVisionAiAppLearning({
76
+ appId: selectedApiId,
77
+ orgId,
78
+ accessToken,
79
+ appUrl,
80
+ userId,
81
+ specUrl,
82
+ chatId,
83
+ modelConfig,
84
+ accountEmail,
85
+ accountPassword,
86
+ });
87
+ const isLocalhost = /localhost|127\.0\.0\.1/i.test(appUrl || "");
88
+ const localhostTip = isLocalhost &&
89
+ "\n\n๐Ÿ’ก **Using localhost?** If the agent reports \"page not loaded\" or \"connection refused\", your app must listen on **0.0.0.0** (not 127.0.0.1). Examples: `npm run dev -- --host`, `HOST=0.0.0.0 npm run dev`, or in your server use `listen(3000, '0.0.0.0')`.";
90
+ const apiPortTip = isLocalhost &&
91
+ "\n\n๐Ÿ’ก **Login/API works in your browser but not in the container?** If your frontend (e.g. port 7846) calls an API on another port (e.g. 7845) using `localhost`, the browser inside the container will send those requests to the container's localhost, not your machineโ€”so you see no 200/404 logs and login fails. **Fix**: (1) Use the same origin for API (e.g. proxy API through the frontend dev server so all requests go to the same port), or (2) Set your app's API base URL to use the same host as the page (e.g. `http://host.docker.internal:7845` when testing from Docker, or use `window.location.hostname` so it works for both).";
92
+ if (result.success) {
93
+ return {
94
+ content: [
95
+ {
96
+ type: "text",
97
+ text: `๐Ÿ“„ **Vision AI App Learning completed successfully**\n\n๐ŸŽฏ **App**: ${selectedApiId}\n๐Ÿ”— **App URL**: ${appUrl}\n๐Ÿข **Organization**: ${orgId}\n\n๐Ÿ“‹ **Output**:\n\`\`\`\n${result.output || "Learning completed โ€” check your PerfAI dashboard or app catalog for the generated spec."}\n\`\`\`\n\nโœ… **Status**: GENERATE_SPEC task finished. Results should be available in your PerfAI dashboard / app catalog.${localhostTip || ""}${apiPortTip || ""}`,
98
+ },
99
+ ],
100
+ };
101
+ }
102
+ return {
103
+ content: [
104
+ {
105
+ type: "text",
106
+ text: `โŒ **Vision AI App Learning failed**\n\n๐Ÿ”ด **Exit code**: ${result.exitCode}\n๐ŸŽฏ **App**: ${selectedApiId}\n๐Ÿ”— **App URL**: ${appUrl}\n\n๐Ÿ“‹ **Error output**:\n\`\`\`\n${result.errors || "Unknown error"}\n\`\`\`\n\n๐Ÿ’ก **Common issues**:\n1. **Docker not running**: Ensure Docker Desktop is running\n2. **Image missing**: Build/pull the image (e.g. set VISION_AI_APP_LEARNING_IMAGE)\n3. **App not reachable / page not loaded**: For \`localhost\`, the container reaches your app via \`host.docker.internal\`. Your app must listen on **0.0.0.0** (e.g. \`npm run dev -- --host\` or \`HOST=0.0.0.0 npm run dev\`). Binding to 127.0.0.1 only will cause connection refused.\n4. **Login works in your browser but not in container / no logs on your server**: Your frontend may call an API on a different port (e.g. 7845) using \`localhost\`. Inside the container, \`localhost\` is the containerโ€”so API requests never reach your machine. Use same-origin (proxy API through frontend) or set API base URL to \`host.docker.internal:API_PORT\` when testing from Docker.\n\n๐Ÿ”„ **Try**: Fix the issues above and run the tool again.`,
107
+ },
108
+ ],
109
+ };
110
+ }
111
+ catch (error) {
112
+ const errorMessage = error instanceof Error ? error.message : String(error);
113
+ return {
114
+ content: [
115
+ {
116
+ type: "text",
117
+ text: `โŒ **Error running Vision AI App Learning**\n\n๐Ÿ”ด **Error**: ${errorMessage}\n\n๐Ÿ’ก **Tip**: Ensure you're authenticated, have an organization and app selected, and provided a valid app_url.\n\n๐Ÿ”„ **Try**: Check your inputs and try again.`,
118
+ },
119
+ ],
120
+ };
121
+ }
122
+ }
@@ -0,0 +1,11 @@
1
+ export declare const authStatusTool: {
2
+ readonly name: "auth_status";
3
+ readonly description: "๐Ÿ“Š Check current authentication status and user information";
4
+ readonly inputSchema: {
5
+ readonly type: "object";
6
+ readonly properties: {};
7
+ readonly additionalProperties: false;
8
+ };
9
+ };
10
+ export declare function handleAuthStatus(authManager: any): Promise<any>;
11
+ //# sourceMappingURL=authStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authStatus.d.ts","sourceRoot":"","sources":["../../../src/tools/public/authStatus.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;;;;;;;;CAQjB,CAAC;AAEX,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAyErE"}