mobbdev 1.0.106 → 1.0.107

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 (2) hide show
  1. package/dist/index.mjs +295 -153
  2. package/package.json +6 -5
package/dist/index.mjs CHANGED
@@ -1062,19 +1062,33 @@ var AutoPrAnalysisDocument = `
1062
1062
  var GetLatestReportByRepoUrlDocument = `
1063
1063
  query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!) {
1064
1064
  fixReport(
1065
- where: {repo: {originalUrl: {_eq: $repoUrl}}}
1065
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
1066
1066
  order_by: {createdOn: desc}
1067
1067
  limit: 1
1068
1068
  ) {
1069
1069
  ...FixReportSummaryFields
1070
1070
  }
1071
+ expiredReport: fixReport(
1072
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
1073
+ order_by: {createdOn: desc}
1074
+ limit: 1
1075
+ ) {
1076
+ id
1077
+ expirationOn
1078
+ }
1071
1079
  }
1072
1080
  ${FixReportSummaryFieldsFragmentDoc}`;
1073
1081
  var GetReportFixesDocument = `
1074
1082
  query GetReportFixes($reportId: uuid!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!) {
1075
- fixReport(where: {id: {_eq: $reportId}}) {
1083
+ fixReport(where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Finished}}]}) {
1076
1084
  ...FixReportSummaryFields
1077
1085
  }
1086
+ expiredReport: fixReport(
1087
+ where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Expired}}]}
1088
+ ) {
1089
+ id
1090
+ expirationOn
1091
+ }
1078
1092
  }
1079
1093
  ${FixReportSummaryFieldsFragmentDoc}`;
1080
1094
  var defaultWrapper = (action, _operationName, _operationType, _variables) => action();
@@ -4824,7 +4838,7 @@ async function getAdoSdk(params) {
4824
4838
  const url = new URL(repoUrl);
4825
4839
  const origin2 = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
4826
4840
  const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
4827
- const path13 = [
4841
+ const path14 = [
4828
4842
  prefixPath,
4829
4843
  owner,
4830
4844
  projectName,
@@ -4835,7 +4849,7 @@ async function getAdoSdk(params) {
4835
4849
  "items",
4836
4850
  "items"
4837
4851
  ].filter(Boolean).join("/");
4838
- return new URL(`${path13}?${params2}`, origin2).toString();
4852
+ return new URL(`${path14}?${params2}`, origin2).toString();
4839
4853
  },
4840
4854
  async getAdoBranchList({ repoUrl }) {
4841
4855
  try {
@@ -5492,7 +5506,6 @@ var GitService = class {
5492
5506
  date: "%ai",
5493
5507
  message: "%s",
5494
5508
  //the field name author_name can't follow the naming convention as we are using the git log command
5495
- // eslint-disable-next-line @typescript-eslint/naming-convention
5496
5509
  author_name: "%an"
5497
5510
  }
5498
5511
  });
@@ -6874,14 +6887,14 @@ function getGithubSdk(params = {}) {
6874
6887
  };
6875
6888
  },
6876
6889
  async getGithubBlameRanges(params2) {
6877
- const { ref, gitHubUrl, path: path13 } = params2;
6890
+ const { ref, gitHubUrl, path: path14 } = params2;
6878
6891
  const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
6879
6892
  const res = await octokit.graphql(
6880
6893
  GET_BLAME_DOCUMENT,
6881
6894
  {
6882
6895
  owner,
6883
6896
  repo,
6884
- path: path13,
6897
+ path: path14,
6885
6898
  ref
6886
6899
  }
6887
6900
  );
@@ -7190,11 +7203,11 @@ var GithubSCMLib = class extends SCMLib {
7190
7203
  markdownComment: comment
7191
7204
  });
7192
7205
  }
7193
- async getRepoBlameRanges(ref, path13) {
7206
+ async getRepoBlameRanges(ref, path14) {
7194
7207
  this._validateUrl();
7195
7208
  return await this.githubSdk.getGithubBlameRanges({
7196
7209
  ref,
7197
- path: path13,
7210
+ path: path14,
7198
7211
  gitHubUrl: this.url
7199
7212
  });
7200
7213
  }
@@ -7600,13 +7613,13 @@ function parseGitlabOwnerAndRepo(gitlabUrl) {
7600
7613
  const { organization, repoName, projectPath } = parsingResult;
7601
7614
  return { owner: organization, repo: repoName, projectPath };
7602
7615
  }
7603
- async function getGitlabBlameRanges({ ref, gitlabUrl, path: path13 }, options) {
7616
+ async function getGitlabBlameRanges({ ref, gitlabUrl, path: path14 }, options) {
7604
7617
  const { projectPath } = parseGitlabOwnerAndRepo(gitlabUrl);
7605
7618
  const api2 = getGitBeaker({
7606
7619
  url: gitlabUrl,
7607
7620
  gitlabAuthToken: options?.gitlabAuthToken
7608
7621
  });
7609
- const resp = await api2.RepositoryFiles.allFileBlames(projectPath, path13, ref);
7622
+ const resp = await api2.RepositoryFiles.allFileBlames(projectPath, path14, ref);
7610
7623
  let lineNumber = 1;
7611
7624
  return resp.filter((range) => range.lines).map((range) => {
7612
7625
  const oldLineNumber = lineNumber;
@@ -7782,10 +7795,10 @@ var GitlabSCMLib = class extends SCMLib {
7782
7795
  markdownComment: comment
7783
7796
  });
7784
7797
  }
7785
- async getRepoBlameRanges(ref, path13) {
7798
+ async getRepoBlameRanges(ref, path14) {
7786
7799
  this._validateUrl();
7787
7800
  return await getGitlabBlameRanges(
7788
- { ref, path: path13, gitlabUrl: this.url },
7801
+ { ref, path: path14, gitlabUrl: this.url },
7789
7802
  {
7790
7803
  url: this.url,
7791
7804
  gitlabAuthToken: this.accessToken
@@ -8784,7 +8797,7 @@ async function postIssueComment(params) {
8784
8797
  fpDescription
8785
8798
  } = params;
8786
8799
  const {
8787
- path: path13,
8800
+ path: path14,
8788
8801
  startLine,
8789
8802
  vulnerabilityReportIssue: {
8790
8803
  vulnerabilityReportIssueTags,
@@ -8799,7 +8812,7 @@ async function postIssueComment(params) {
8799
8812
  Refresh the page in order to see the changes.`,
8800
8813
  pull_number: pullRequest,
8801
8814
  commit_id: commitSha,
8802
- path: path13,
8815
+ path: path14,
8803
8816
  line: startLine
8804
8817
  });
8805
8818
  const commentId = commentRes.data.id;
@@ -8833,7 +8846,7 @@ async function postFixComment(params) {
8833
8846
  scanner
8834
8847
  } = params;
8835
8848
  const {
8836
- path: path13,
8849
+ path: path14,
8837
8850
  startLine,
8838
8851
  vulnerabilityReportIssue: { fixId, vulnerabilityReportIssueTags, category },
8839
8852
  vulnerabilityReportIssueId
@@ -8851,7 +8864,7 @@ async function postFixComment(params) {
8851
8864
  Refresh the page in order to see the changes.`,
8852
8865
  pull_number: pullRequest,
8853
8866
  commit_id: commitSha,
8854
- path: path13,
8867
+ path: path14,
8855
8868
  line: startLine
8856
8869
  });
8857
8870
  const commentId = commentRes.data.id;
@@ -11222,7 +11235,8 @@ var Logger = class {
11222
11235
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11223
11236
  level: logEntry.level,
11224
11237
  message: logEntry.message,
11225
- data: logEntry.data
11238
+ data: logEntry.data,
11239
+ version: packageJson.version
11226
11240
  };
11227
11241
  const controller = new AbortController();
11228
11242
  const timeoutId = setTimeout(() => {
@@ -11279,7 +11293,7 @@ import { v4 as uuidv42 } from "uuid";
11279
11293
  var DEFAULT_API_URL2 = "https://api.mobb.ai/v1/graphql";
11280
11294
  var API_KEY_HEADER_NAME2 = "x-mobb-key";
11281
11295
 
11282
- // src/mcp/tools/fixVulnerabilities/errors/VulnerabilityFixErrors.ts
11296
+ // src/mcp/core/Errors.ts
11283
11297
  var ApiConnectionError = class extends Error {
11284
11298
  constructor(message = "Failed to connect to the API") {
11285
11299
  super(message);
@@ -11354,6 +11368,7 @@ var McpGQLClient = class {
11354
11368
  __publicField(this, "_auth");
11355
11369
  this._auth = args;
11356
11370
  const API_URL2 = process.env["API_URL"] || DEFAULT_API_URL2;
11371
+ logDebug("creating graphql client", { API_URL: API_URL2, args });
11357
11372
  this.client = new GraphQLClient2(API_URL2, {
11358
11373
  headers: args.type === "apiKey" ? { [API_KEY_HEADER_NAME2]: args.apiKey || "" } : {
11359
11374
  Authorization: `Bearer ${args.token}`
@@ -11385,11 +11400,13 @@ var McpGQLClient = class {
11385
11400
  try {
11386
11401
  logDebug("GraphQL: Calling Me query for connection verification");
11387
11402
  const result = await this.clientSdk.Me();
11388
- logInfo("GraphQL: Me query successful", { result });
11403
+ logDebug("GraphQL: Me query successful", { result });
11389
11404
  return true;
11390
11405
  } catch (e) {
11391
- if (e?.toString().includes("FetchError")) {
11392
- logError("verify connection failed %o", e);
11406
+ const error = e;
11407
+ logDebug(`verify connection failed ${error.toString()}`);
11408
+ if (error?.toString().includes("FetchError")) {
11409
+ logError("verify connection failed", { error });
11393
11410
  return false;
11394
11411
  }
11395
11412
  }
@@ -11606,7 +11623,10 @@ var McpGQLClient = class {
11606
11623
  result: res,
11607
11624
  reportCount: res.fixReport?.length || 0
11608
11625
  });
11609
- return res.fixReport?.[0] || null;
11626
+ return {
11627
+ fixReport: res.fixReport?.[0] || null,
11628
+ expiredReport: res.expiredReport?.[0] || null
11629
+ };
11610
11630
  } catch (e) {
11611
11631
  logError("GraphQL: GetLatestReportByRepoUrl failed", {
11612
11632
  error: e,
@@ -11655,7 +11675,8 @@ var McpGQLClient = class {
11655
11675
  }
11656
11676
  return {
11657
11677
  fixes: res.fixReport?.[0]?.fixes || [],
11658
- totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0
11678
+ totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
11679
+ expiredReport: res.expiredReport?.[0] || null
11659
11680
  };
11660
11681
  } catch (e) {
11661
11682
  logError("GraphQL: GetReportFixes failed", {
@@ -11680,17 +11701,21 @@ async function openBrowser(url) {
11680
11701
  async function getMcpGQLClient() {
11681
11702
  logDebug("getting config", { apiToken: config4.get("apiToken") });
11682
11703
  const inGqlClient = new McpGQLClient({
11683
- apiKey: config4.get("apiToken") || process.env["API_KEY"] || "",
11704
+ apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
11705
+ config4.get("apiToken") || "",
11684
11706
  type: "apiKey"
11685
11707
  });
11686
11708
  const isConnected = await inGqlClient.verifyConnection();
11709
+ logDebug("isConnected", { isConnected });
11687
11710
  if (!isConnected) {
11688
11711
  throw new ApiConnectionError("Error: failed to connect to Mobb API");
11689
11712
  }
11713
+ logDebug("verifying token");
11690
11714
  const userVerify = await inGqlClient.verifyToken();
11691
11715
  if (userVerify) {
11692
11716
  return inGqlClient;
11693
11717
  }
11718
+ logDebug("verifying token failed");
11694
11719
  const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
11695
11720
  modulusLength: 2048
11696
11721
  });
@@ -11838,15 +11863,9 @@ var McpServer = class {
11838
11863
  }
11839
11864
  async handleListToolsRequest(request) {
11840
11865
  logInfo("Received list_tools request", { params: request.params });
11841
- logInfo("Environment", {
11842
- env: process.env
11843
- });
11844
11866
  logInfo("Request", {
11845
11867
  request: JSON.parse(JSON.stringify(request))
11846
11868
  });
11847
- logInfo("Server", {
11848
- server: this.server
11849
- });
11850
11869
  void getMcpGQLClient();
11851
11870
  const toolsDefinitions = this.toolRegistry.getAllTools();
11852
11871
  const response = {
@@ -11867,15 +11886,9 @@ var McpServer = class {
11867
11886
  async handleCallToolRequest(request) {
11868
11887
  const { name, arguments: args } = request.params;
11869
11888
  logInfo(`Received call tool request for ${name}`, { name, args });
11870
- logInfo("Environment", {
11871
- env: process.env
11872
- });
11873
11889
  logInfo("Request", {
11874
11890
  request: JSON.parse(JSON.stringify(request))
11875
11891
  });
11876
- logInfo("Server", {
11877
- server: this.server
11878
- });
11879
11892
  try {
11880
11893
  const tool = this.toolRegistry.getTool(name);
11881
11894
  if (!tool) {
@@ -11938,53 +11951,71 @@ var McpServer = class {
11938
11951
  }
11939
11952
  };
11940
11953
 
11941
- // src/mcp/tools/checkForAvailableFixes/AvailableFixesTool.ts
11954
+ // src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesTool.ts
11942
11955
  import { z as z32 } from "zod";
11943
11956
 
11944
11957
  // src/mcp/services/PathValidation.ts
11945
11958
  import fs9 from "fs";
11946
11959
  import path11 from "path";
11947
- var PathValidation = class {
11948
- /**
11949
- * Validates a path for MCP usage - combines security and existence checks
11950
- */
11951
- async validatePath(inputPath) {
11952
- logDebug("Validating MCP path", { inputPath });
11953
- if (inputPath.includes("..")) {
11954
- const error = `Path contains path traversal patterns: ${inputPath}`;
11955
- logError(error);
11956
- return { isValid: false, error };
11957
- }
11958
- const normalizedPath = path11.normalize(inputPath);
11959
- if (normalizedPath.includes("..")) {
11960
- const error = `Normalized path contains path traversal patterns: ${inputPath}`;
11961
- logError(error);
11962
- return { isValid: false, error };
11963
- }
11964
- const decodedPath = decodeURIComponent(inputPath);
11965
- if (decodedPath.includes("..") || decodedPath !== inputPath) {
11966
- const error = `Path contains encoded traversal attempts: ${inputPath}`;
11967
- logError(error);
11968
- return { isValid: false, error };
11969
- }
11970
- if (inputPath.includes("\0") || inputPath.includes("\0")) {
11971
- const error = `Path contains dangerous characters: ${inputPath}`;
11960
+ async function validatePath(inputPath) {
11961
+ logDebug("Validating MCP path", { inputPath });
11962
+ if (inputPath === ".") {
11963
+ if (process.env["WORKSPACE_FOLDER_PATHS"]) {
11964
+ logDebug("Fallback to workspace folder path", {
11965
+ inputPath,
11966
+ workspaceFolderPaths: process.env["WORKSPACE_FOLDER_PATHS"]
11967
+ });
11968
+ return {
11969
+ isValid: true,
11970
+ path: process.env["WORKSPACE_FOLDER_PATHS"]
11971
+ };
11972
+ } else {
11973
+ const error = `"." is not a valid path, please provide a full localpath to the repository`;
11972
11974
  logError(error);
11973
- return { isValid: false, error };
11974
- }
11975
- logDebug("Path validation successful", { inputPath });
11976
- logDebug("Checking path existence", { inputPath });
11977
- try {
11978
- await fs9.promises.access(inputPath);
11979
- logDebug("Path exists and is accessible", { inputPath });
11980
- return { isValid: true };
11981
- } catch (error) {
11982
- const errorMessage = `Path does not exist or is not accessible: ${inputPath}`;
11983
- logError(errorMessage, { error });
11984
- return { isValid: false, error: errorMessage };
11975
+ return { isValid: false, error, path: inputPath };
11985
11976
  }
11986
11977
  }
11987
- };
11978
+ if (inputPath.includes("..")) {
11979
+ const error = `Path contains path traversal patterns: ${inputPath}`;
11980
+ logError(error);
11981
+ return { isValid: false, error, path: inputPath };
11982
+ }
11983
+ const normalizedPath = path11.normalize(inputPath);
11984
+ if (normalizedPath.includes("..")) {
11985
+ const error = `Normalized path contains path traversal patterns: ${inputPath}`;
11986
+ logError(error);
11987
+ return { isValid: false, error, path: inputPath };
11988
+ }
11989
+ let decodedPath;
11990
+ try {
11991
+ decodedPath = decodeURIComponent(inputPath);
11992
+ } catch (err) {
11993
+ const error = `Failed to decode path: ${inputPath}`;
11994
+ logError(error, { err });
11995
+ return { isValid: false, error, path: inputPath };
11996
+ }
11997
+ if (decodedPath.includes("..") || decodedPath !== inputPath) {
11998
+ const error = `Path contains encoded traversal attempts: ${inputPath}`;
11999
+ logError(error);
12000
+ return { isValid: false, error, path: inputPath };
12001
+ }
12002
+ if (inputPath.includes("\0") || inputPath.includes("\0")) {
12003
+ const error = `Path contains dangerous characters: ${inputPath}`;
12004
+ logError(error);
12005
+ return { isValid: false, error, path: inputPath };
12006
+ }
12007
+ logDebug("Path validation successful", { inputPath });
12008
+ logDebug("Checking path existence", { inputPath });
12009
+ try {
12010
+ await fs9.promises.access(inputPath);
12011
+ logDebug("Path exists and is accessible", { inputPath });
12012
+ return { isValid: true, path: inputPath };
12013
+ } catch (error) {
12014
+ const errorMessage = `Path does not exist or is not accessible: ${inputPath}`;
12015
+ logError(errorMessage, { error });
12016
+ return { isValid: false, error: errorMessage, path: inputPath };
12017
+ }
12018
+ }
11988
12019
 
11989
12020
  // src/mcp/tools/base/BaseTool.ts
11990
12021
  import { z as z31 } from "zod";
@@ -12053,7 +12084,8 @@ var applyFixesPrompt = ({
12053
12084
  totalCount,
12054
12085
  nextOffset,
12055
12086
  shownCount,
12056
- currentTool
12087
+ currentTool,
12088
+ offset = 0
12057
12089
  }) => {
12058
12090
  if (fixes.length === 0) {
12059
12091
  return noFixesReturnedForParameters;
@@ -12111,7 +12143,7 @@ If you cannot apply a patch:
12111
12143
 
12112
12144
  ${fixList.map(
12113
12145
  (fix, index) => `
12114
- ## Fix ${index + 1}: ${fix.vulnerabilityType}
12146
+ ## Fix ${offset + index + 1}: ${fix.vulnerabilityType}
12115
12147
 
12116
12148
  **\u{1F3AF} Target:** Apply this patch to fix a ${fix.vulnerabilityType.toLowerCase()} vulnerability
12117
12149
 
@@ -12164,7 +12196,7 @@ We were unable to find a previous vulnerability report for this repository. This
12164
12196
 
12165
12197
  ### \u{1F3AF} Recommended Actions
12166
12198
  1. **Run a new security scan** to analyze your codebase
12167
- - Use the \`fix_vulnerabilities\` tool to start a fresh scan
12199
+ - Use the \`scan_and_fix_vulnerabilities\` tool to start a fresh scan
12168
12200
  - This will analyze your current code for security issues
12169
12201
 
12170
12202
  2. **Verify repository access**
@@ -12178,6 +12210,27 @@ We were unable to find a previous vulnerability report for this repository. This
12178
12210
  For assistance, please:
12179
12211
  - Visit our documentation at https://docs.mobb.ai
12180
12212
  - Contact support at support@mobb.ai`;
12213
+ var expiredReportPrompt = ({
12214
+ lastReportDate
12215
+ }) => `\u{1F50D} **MOBB SECURITY SCAN STATUS**
12216
+
12217
+ ## Out-of-Date Vulnerability Report
12218
+
12219
+ Your most recent vulnerability report for this repository **expired on ${lastReportDate}** and is no longer available for fetching automated fixes.
12220
+
12221
+ ### \u{1F4CB} Why Did This Happen?
12222
+ - Reports are automatically purged after a retention period for security and storage optimisation.
12223
+ - No new scans have been run since the last report expired.
12224
+
12225
+ ### \u{1F3AF} Recommended Actions
12226
+ 1. **Run a fresh security scan** to generate an up-to-date vulnerability report.
12227
+ - Use the \`scan_and_fix_vulnerabilities\` tool.
12228
+ 2. **Verify repository access** if scans fail to run or the repository has moved.
12229
+ 3. **Review your CI/CD pipeline** to ensure regular scans are triggered.
12230
+
12231
+ For more help:
12232
+ - Documentation: https://docs.mobb.ai
12233
+ - Support: support@mobb.ai`;
12181
12234
  var noFixesAvailablePrompt = `There are no fixes available for this repository at this time.
12182
12235
  `;
12183
12236
  var fixesFoundPrompt = ({
@@ -12225,7 +12278,8 @@ ${applyFixesPrompt({
12225
12278
  hasMore,
12226
12279
  nextOffset: 0,
12227
12280
  shownCount: fixReport.fixes.length,
12228
- currentTool: "check_for_available_fixes"
12281
+ currentTool: "fetch_available_fixes",
12282
+ offset
12229
12283
  })}`;
12230
12284
  };
12231
12285
  var noFixesFoundPrompt = `\u{1F50D} **MOBB SECURITY SCAN COMPLETED**
@@ -12235,7 +12289,8 @@ Mobb security scan completed successfully but found no automated fixes available
12235
12289
  var fixesPrompt = ({
12236
12290
  fixes,
12237
12291
  totalCount,
12238
- offset
12292
+ offset,
12293
+ scannedFiles
12239
12294
  }) => {
12240
12295
  if (totalCount === 0) {
12241
12296
  return noFixesFoundPrompt;
@@ -12251,9 +12306,13 @@ ${applyFixesPrompt({
12251
12306
  hasMore,
12252
12307
  nextOffset,
12253
12308
  shownCount,
12254
- currentTool: "fix_vulnerabilities"
12309
+ currentTool: "scan_and_fix_vulnerabilities",
12310
+ offset
12255
12311
  })}
12256
12312
 
12313
+ ### \u{1F4C1} Scanned Files
12314
+ ${scannedFiles.map((file) => `- ${file}`).join("\n")}
12315
+
12257
12316
  ### \u{1F504} Running a Fresh Scan
12258
12317
 
12259
12318
  To perform a **rescan** of your repository (fetching a brand-new vulnerability report and updated fixes), include the additional parameter:
@@ -12267,12 +12326,21 @@ This will start a new analysis, discard any cached results.
12267
12326
  `;
12268
12327
  };
12269
12328
 
12270
- // src/mcp/tools/checkForAvailableFixes/AvailableFixesService.ts
12271
- var AvailableFixesService = class {
12329
+ // src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesService.ts
12330
+ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
12272
12331
  constructor() {
12273
12332
  __publicField(this, "gqlClient", null);
12274
12333
  __publicField(this, "currentOffset", 0);
12275
12334
  }
12335
+ static getInstance() {
12336
+ if (!_FetchAvailableFixesService.instance) {
12337
+ _FetchAvailableFixesService.instance = new _FetchAvailableFixesService();
12338
+ }
12339
+ return _FetchAvailableFixesService.instance;
12340
+ }
12341
+ reset() {
12342
+ this.currentOffset = 0;
12343
+ }
12276
12344
  async initializeGqlClient() {
12277
12345
  if (!this.gqlClient) {
12278
12346
  this.gqlClient = await getMcpGQLClient();
@@ -12282,40 +12350,44 @@ var AvailableFixesService = class {
12282
12350
  async checkForAvailableFixes({
12283
12351
  repoUrl,
12284
12352
  limit = 3,
12285
- offset = 0
12353
+ offset
12286
12354
  }) {
12287
12355
  try {
12288
12356
  logDebug("Checking for available fixes", { repoUrl, limit });
12289
12357
  const gqlClient = await this.initializeGqlClient();
12290
12358
  logDebug("GQL client initialized");
12291
12359
  logDebug("querying for latest report", { repoUrl, limit });
12292
- let effectiveOffset;
12293
- if (offset !== void 0) {
12294
- effectiveOffset = offset;
12295
- } else if (this.currentOffset) {
12296
- effectiveOffset = this.currentOffset ?? 0;
12297
- } else {
12298
- effectiveOffset = 0;
12299
- }
12300
- logDebug("effectiveOffset", { test: "j", effectiveOffset });
12301
- const result = await gqlClient.getLatestReportByRepoUrl({
12360
+ const effectiveOffset = offset ?? (this.currentOffset || 0);
12361
+ logDebug("effectiveOffset", { effectiveOffset });
12362
+ const { fixReport, expiredReport } = await gqlClient.getLatestReportByRepoUrl({
12302
12363
  repoUrl,
12303
12364
  limit,
12304
12365
  offset: effectiveOffset
12305
12366
  });
12306
- logDebug("received latest report result", { result });
12307
- if (!result) {
12308
- logInfo("No report found for repository", { repoUrl });
12367
+ logDebug("received latest report result", { fixReport, expiredReport });
12368
+ if (!fixReport) {
12369
+ if (expiredReport) {
12370
+ const lastReportDate = expiredReport.expirationOn ? new Date(expiredReport.expirationOn).toLocaleString() : "Unknown date";
12371
+ logInfo("Expired report found", {
12372
+ repoUrl,
12373
+ expirationOn: expiredReport.expirationOn
12374
+ });
12375
+ return expiredReportPrompt({ lastReportDate });
12376
+ }
12377
+ logInfo("No report (active or expired) found for repository", {
12378
+ repoUrl
12379
+ });
12309
12380
  return noReportFoundPrompt;
12310
12381
  }
12311
12382
  logInfo("Successfully retrieved available fixes", {
12312
12383
  reportFound: true
12313
12384
  });
12314
- this.currentOffset = effectiveOffset + (result.fixes?.length || 0);
12315
- return fixesFoundPrompt({
12316
- fixReport: result,
12317
- offset: this.currentOffset
12385
+ const prompt = fixesFoundPrompt({
12386
+ fixReport,
12387
+ offset: effectiveOffset
12318
12388
  });
12389
+ this.currentOffset = effectiveOffset + (fixReport.fixes?.length || 0);
12390
+ return prompt;
12319
12391
  } catch (error) {
12320
12392
  logError("Failed to check for available fixes", {
12321
12393
  error,
@@ -12325,20 +12397,40 @@ var AvailableFixesService = class {
12325
12397
  }
12326
12398
  }
12327
12399
  };
12400
+ __publicField(_FetchAvailableFixesService, "instance");
12401
+ var FetchAvailableFixesService = _FetchAvailableFixesService;
12328
12402
 
12329
- // src/mcp/tools/checkForAvailableFixes/AvailableFixesTool.ts
12330
- var CheckForAvailableFixesTool = class extends BaseTool {
12403
+ // src/mcp/tools/fetchAvailableFixes/FetchAvailableFixesTool.ts
12404
+ var FetchAvailableFixesTool = class extends BaseTool {
12331
12405
  constructor() {
12332
- super(...arguments);
12333
- __publicField(this, "name", "check_for_available_fixes");
12334
- __publicField(this, "displayName", "Check for Available Fixes");
12335
- __publicField(this, "description", "Checks if there are any available fixes for vulnerabilities in the project");
12406
+ super();
12407
+ __publicField(this, "name", "fetch_available_fixes");
12408
+ __publicField(this, "displayName", "Fetch Available Fixes");
12409
+ __publicField(this, "description", `Check the MOBB backend for pre-generated fixes (patch sets) that correspond to vulnerabilities detected in the supplied Git repository.
12410
+
12411
+ Use when:
12412
+ \u2022 You already have a local clone of a Git repository and want to know if MOBB has fixes available for it.
12413
+ \u2022 A vulnerability scan has been run previously and uploaded to the MOBB backend and you want to fetch the list or count of ready-to-apply fixes before triggering a full scan-and-fix flow.
12414
+
12415
+ Required argument:
12416
+ \u2022 path \u2013 absolute path to the local Git repository clone.
12417
+
12418
+ Optional arguments:
12419
+ \u2022 offset \u2013 pagination offset (integer).
12420
+ \u2022 limit \u2013 maximum number of fixes to return (integer).
12421
+
12422
+ The tool will:
12423
+ 1. Validate that the provided path is secure and exists.
12424
+ 2. Verify that the directory is a valid Git repository with an "origin" remote.
12425
+ 3. Query the MOBB service by the origin remote URL and return a textual summary of available fixes (total and by severity) or a message if none are found.
12426
+
12427
+ Call this tool instead of scan_and_fix_vulnerabilities when you only need a fixes summary and do NOT want to perform scanning or code modifications.`);
12336
12428
  __publicField(this, "inputSchema", {
12337
12429
  type: "object",
12338
12430
  properties: {
12339
12431
  path: {
12340
12432
  type: "string",
12341
- description: "Path to the local git repository to check for available fixes"
12433
+ description: "Full local path to the cloned git repository to check for available fixes"
12342
12434
  },
12343
12435
  offset: {
12344
12436
  type: "number",
@@ -12353,21 +12445,24 @@ var CheckForAvailableFixesTool = class extends BaseTool {
12353
12445
  });
12354
12446
  __publicField(this, "inputValidationSchema", z32.object({
12355
12447
  path: z32.string().describe(
12356
- "Path to the local git repository to check for available fixes"
12448
+ "Full local path to the cloned git repository to check for available fixes"
12357
12449
  ),
12358
12450
  offset: z32.number().optional().describe("Optional offset for pagination"),
12359
12451
  limit: z32.number().optional().describe("Optional maximum number of fixes to return")
12360
12452
  }));
12453
+ __publicField(this, "availableFixesService");
12454
+ this.availableFixesService = FetchAvailableFixesService.getInstance();
12455
+ this.availableFixesService.reset();
12361
12456
  }
12362
12457
  async executeInternal(args) {
12363
- const pathValidation = new PathValidation();
12364
- const pathValidationResult = await pathValidation.validatePath(args.path);
12458
+ const pathValidationResult = await validatePath(args.path);
12365
12459
  if (!pathValidationResult.isValid) {
12366
12460
  throw new Error(
12367
12461
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
12368
12462
  );
12369
12463
  }
12370
- const gitService = new GitService(args.path, log);
12464
+ const path14 = pathValidationResult.path;
12465
+ const gitService = new GitService(path14, log);
12371
12466
  const gitValidation = await gitService.validateRepository();
12372
12467
  if (!gitValidation.isValid) {
12373
12468
  throw new Error(`Invalid git repository: ${gitValidation.error}`);
@@ -12380,13 +12475,12 @@ var CheckForAvailableFixesTool = class extends BaseTool {
12380
12475
  if (!originUrl) {
12381
12476
  throw new Error("No origin URL found for the repository");
12382
12477
  }
12383
- const availableFixesService = new AvailableFixesService();
12384
- const fixResult = await availableFixesService.checkForAvailableFixes({
12478
+ const fixResult = await this.availableFixesService.checkForAvailableFixes({
12385
12479
  repoUrl: originUrl,
12386
12480
  limit: args.limit,
12387
12481
  offset: args.offset
12388
12482
  });
12389
- logInfo("CheckForAvailableFixesTool execution completed successfully", {
12483
+ logInfo("FetchAvailableFixesTool execution completed successfully", {
12390
12484
  fixResult
12391
12485
  });
12392
12486
  return {
@@ -12400,9 +12494,12 @@ var CheckForAvailableFixesTool = class extends BaseTool {
12400
12494
  }
12401
12495
  };
12402
12496
 
12403
- // src/mcp/tools/fixVulnerabilities/FixVulnerabilitiesTool.ts
12497
+ // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
12404
12498
  import z33 from "zod";
12405
12499
 
12500
+ // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
12501
+ import path13 from "path";
12502
+
12406
12503
  // src/mcp/services/FilePacking.ts
12407
12504
  import fs10 from "fs";
12408
12505
  import path12 from "path";
@@ -12443,9 +12540,9 @@ var FilePacking = class {
12443
12540
  }
12444
12541
  };
12445
12542
 
12446
- // src/mcp/tools/fixVulnerabilities/FixVulnerabilitiesService.ts
12543
+ // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesService.ts
12447
12544
  var VUL_REPORT_DIGEST_TIMEOUT_MS2 = 1e3 * 60 * 5;
12448
- var VulnerabilityFixService = class {
12545
+ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService {
12449
12546
  constructor() {
12450
12547
  __publicField(this, "gqlClient");
12451
12548
  __publicField(this, "filePacking");
@@ -12458,6 +12555,16 @@ var VulnerabilityFixService = class {
12458
12555
  __publicField(this, "currentOffset", 0);
12459
12556
  this.filePacking = new FilePacking();
12460
12557
  }
12558
+ static getInstance() {
12559
+ if (!_ScanAndFixVulnerabilitiesService.instance) {
12560
+ _ScanAndFixVulnerabilitiesService.instance = new _ScanAndFixVulnerabilitiesService();
12561
+ }
12562
+ return _ScanAndFixVulnerabilitiesService.instance;
12563
+ }
12564
+ reset() {
12565
+ this.storedFixReportId = void 0;
12566
+ this.currentOffset = void 0;
12567
+ }
12461
12568
  async processVulnerabilities({
12462
12569
  fileList,
12463
12570
  repositoryPath,
@@ -12467,8 +12574,13 @@ var VulnerabilityFixService = class {
12467
12574
  }) {
12468
12575
  try {
12469
12576
  this.gqlClient = await this.initializeGqlClient();
12577
+ logInfo("storedFixReportId", {
12578
+ storedFixReportId: this.storedFixReportId,
12579
+ currentOffset: this.currentOffset
12580
+ });
12470
12581
  let fixReportId = this.storedFixReportId;
12471
12582
  if (!fixReportId || isRescan) {
12583
+ this.reset();
12472
12584
  this.validateFiles(fileList);
12473
12585
  const repoUploadInfo = await this.initializeReport();
12474
12586
  fixReportId = repoUploadInfo.fixReportId;
@@ -12478,26 +12590,21 @@ var VulnerabilityFixService = class {
12478
12590
  const projectId = await this.getProjectId();
12479
12591
  await this.runScan({ fixReportId, projectId });
12480
12592
  }
12481
- let effectiveOffset;
12482
- if (offset !== void 0) {
12483
- effectiveOffset = offset;
12484
- } else if (fixReportId) {
12485
- effectiveOffset = this.currentOffset ?? 0;
12486
- } else {
12487
- effectiveOffset = 0;
12488
- }
12593
+ const effectiveOffset = offset ?? (this.currentOffset || 0);
12489
12594
  logDebug("effectiveOffset", { effectiveOffset });
12490
12595
  const fixes = await this.getReportFixes(
12491
12596
  fixReportId,
12492
12597
  effectiveOffset,
12493
12598
  limit
12494
12599
  );
12495
- this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0);
12496
- return fixesPrompt({
12600
+ const prompt = fixesPrompt({
12497
12601
  fixes: fixes.fixes,
12498
12602
  totalCount: fixes.totalCount,
12499
- offset: effectiveOffset
12603
+ offset: effectiveOffset,
12604
+ scannedFiles: fileList.map((file) => path13.basename(file))
12500
12605
  });
12606
+ this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0);
12607
+ return prompt;
12501
12608
  } catch (error) {
12502
12609
  const message = error.message;
12503
12610
  logError("Vulnerability processing failed", { error: message });
@@ -12514,7 +12621,7 @@ var VulnerabilityFixService = class {
12514
12621
  const isConnected = await gqlClient.verifyConnection();
12515
12622
  if (!isConnected) {
12516
12623
  throw new ApiConnectionError(
12517
- "Failed to connect to the API. Please check your API_KEY"
12624
+ "Failed to connect to the API. Please check your MOBB_API_KEY"
12518
12625
  );
12519
12626
  }
12520
12627
  return gqlClient;
@@ -12631,17 +12738,50 @@ var VulnerabilityFixService = class {
12631
12738
  };
12632
12739
  }
12633
12740
  };
12741
+ __publicField(_ScanAndFixVulnerabilitiesService, "instance");
12742
+ var ScanAndFixVulnerabilitiesService = _ScanAndFixVulnerabilitiesService;
12634
12743
 
12635
- // src/mcp/tools/fixVulnerabilities/FixVulnerabilitiesTool.ts
12636
- var FixVulnerabilitiesTool = class extends BaseTool {
12744
+ // src/mcp/tools/scanAndFixVulnerabilities/ScanAndFixVulnerabilitiesTool.ts
12745
+ var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
12637
12746
  constructor() {
12638
- super(...arguments);
12639
- __publicField(this, "name", "fix_vulnerabilities");
12640
- __publicField(this, "displayName", "Fix Vulnerabilities");
12641
- __publicField(this, "description", "Scans the current code changes and returns fixes for potential vulnerabilities");
12747
+ super();
12748
+ __publicField(this, "name", "scan_and_fix_vulnerabilities");
12749
+ __publicField(this, "displayName", "Scan and Fix Vulnerabilities");
12750
+ // A detailed description to guide the LLM on when and how to invoke this tool.
12751
+ __publicField(this, "description", `Scans a given local repository for security vulnerabilities and returns auto-generated code fixes.
12752
+
12753
+ When to invoke:
12754
+ \u2022 Use when the user explicitly asks to "scan for vulnerabilities", "run a security check", or "test for security issues" in a local repository.
12755
+ \u2022 The repository must exist on disk; supply its absolute path with the required "path" argument.
12756
+ \u2022 Ideal after the user makes code changes (added/modified/staged files) but before committing, or whenever they request a full rescan.
12757
+
12758
+ How to invoke:
12759
+ \u2022 Required argument:
12760
+ \u2013 path (string): absolute path to the repository root.
12761
+ \u2022 Optional arguments:
12762
+ \u2013 offset (number): pagination offset used when the result set is large.
12763
+ \u2013 limit (number): maximum number of fixes to include in the response.
12764
+ \u2013 rescan (boolean): true to force a complete rescan even if cached results exist.
12765
+
12766
+ Behaviour:
12767
+ \u2022 If the directory is not a valid Git repository, the tool falls back to scanning recently changed files in the folder.
12768
+ \u2022 By default, only new, modified, or staged files are scanned; if none are found, it checks recently changed files.
12769
+ \u2022 The tool NEVER commits or pushes changes; it only returns proposed diffs/fixes as text.
12770
+
12771
+ Return value:
12772
+ The response is an object with a single "content" array containing one text element. The text is either:
12773
+ \u2022 A human-readable summary of the fixes / patches, or
12774
+ \u2022 A diagnostic or error message if the scan fails or finds nothing to fix.
12775
+
12776
+ Example payload:
12777
+ {
12778
+ "path": "/home/user/my-project",
12779
+ "limit": 20,
12780
+ "rescan": false
12781
+ }`);
12642
12782
  __publicField(this, "inputValidationSchema", z33.object({
12643
12783
  path: z33.string().describe(
12644
- "Path to the local git repository to check for available fixes"
12784
+ "Full local path to repository to scan and fix vulnerabilities"
12645
12785
  ),
12646
12786
  offset: z33.number().optional().describe("Optional offset for pagination"),
12647
12787
  limit: z33.number().optional().describe("Optional maximum number of results to return"),
@@ -12652,7 +12792,7 @@ var FixVulnerabilitiesTool = class extends BaseTool {
12652
12792
  properties: {
12653
12793
  path: {
12654
12794
  type: "string",
12655
- description: "Path to the project directory to check for available fixes"
12795
+ description: "Full local path to repository to scan and fix vulnerabilities"
12656
12796
  },
12657
12797
  offset: {
12658
12798
  type: "number",
@@ -12669,30 +12809,33 @@ var FixVulnerabilitiesTool = class extends BaseTool {
12669
12809
  },
12670
12810
  required: ["path"]
12671
12811
  });
12812
+ __publicField(this, "vulnerabilityFixService");
12813
+ this.vulnerabilityFixService = ScanAndFixVulnerabilitiesService.getInstance();
12814
+ this.vulnerabilityFixService.reset();
12672
12815
  }
12673
12816
  async executeInternal(args) {
12674
- logInfo("Executing tool: fix_vulnerabilities", { path: args.path });
12817
+ logInfo("Executing tool: scan_and_fix_vulnerabilities", { path: args.path });
12675
12818
  if (!args.path) {
12676
12819
  throw new Error("Invalid arguments: Missing required parameter 'path'");
12677
12820
  }
12678
- const pathValidation = new PathValidation();
12679
- const pathValidationResult = await pathValidation.validatePath(args.path);
12821
+ const pathValidationResult = await validatePath(args.path);
12680
12822
  if (!pathValidationResult.isValid) {
12681
12823
  throw new Error(
12682
12824
  `Invalid path: potential security risk detected in path: ${pathValidationResult.error}`
12683
12825
  );
12684
12826
  }
12685
- const gitService = new GitService(args.path, log);
12827
+ const path14 = pathValidationResult.path;
12828
+ const gitService = new GitService(path14, log);
12686
12829
  const gitValidation = await gitService.validateRepository();
12687
12830
  let files = [];
12688
12831
  if (!gitValidation.isValid) {
12689
12832
  logDebug(
12690
12833
  "Git repository validation failed, using all files in the repository",
12691
12834
  {
12692
- path: args.path
12835
+ path: path14
12693
12836
  }
12694
12837
  );
12695
- files = FileUtils.getLastChangedFiles(args.path);
12838
+ files = FileUtils.getLastChangedFiles(path14);
12696
12839
  logDebug("Found files in the repository", {
12697
12840
  files,
12698
12841
  fileCount: files.length
@@ -12729,10 +12872,9 @@ var FixVulnerabilitiesTool = class extends BaseTool {
12729
12872
  };
12730
12873
  }
12731
12874
  try {
12732
- const vulnerabilityFixService = new VulnerabilityFixService();
12733
- const fixResult = await vulnerabilityFixService.processVulnerabilities({
12875
+ const fixResult = await this.vulnerabilityFixService.processVulnerabilities({
12734
12876
  fileList: files,
12735
- repositoryPath: args.path,
12877
+ repositoryPath: path14,
12736
12878
  offset: args.offset,
12737
12879
  limit: args.limit,
12738
12880
  isRescan: args.rescan
@@ -12774,7 +12916,7 @@ function createMcpServer() {
12774
12916
  logDebug("Creating MCP server");
12775
12917
  const server = new McpServer({
12776
12918
  name: "mobb-mcp",
12777
- version: "1.0.0"
12919
+ version: packageJson.version
12778
12920
  });
12779
12921
  const enabledToolsEnv = process.env["TOOLS_ENABLED"];
12780
12922
  const enabledToolsSet = enabledToolsEnv ? new Set(
@@ -12788,10 +12930,10 @@ function createMcpServer() {
12788
12930
  logDebug(`Skipping tool (disabled): ${tool.name}`);
12789
12931
  }
12790
12932
  };
12791
- const fixVulnerabilitiesTool = new FixVulnerabilitiesTool();
12792
- const checkForAvailableFixesTool = new CheckForAvailableFixesTool();
12793
- registerIfEnabled(fixVulnerabilitiesTool);
12794
- registerIfEnabled(checkForAvailableFixesTool);
12933
+ const scanAndFixVulnerabilitiesTool = new ScanAndFixVulnerabilitiesTool();
12934
+ const fetchAvailableFixesTool = new FetchAvailableFixesTool();
12935
+ registerIfEnabled(scanAndFixVulnerabilitiesTool);
12936
+ registerIfEnabled(fetchAvailableFixesTool);
12795
12937
  logInfo("MCP server created and configured");
12796
12938
  return server;
12797
12939
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.0.106",
3
+ "version": "1.0.107",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.js",
@@ -15,6 +15,7 @@
15
15
  "test:mcp": "pnpm run build && pnpm run test:mcp:unit",
16
16
  "test:mcp:watch": "vitest watch __tests__/mcp/mcp.unit.test.ts",
17
17
  "test:mcp:verbose": "pnpm run build && NODE_ENV=test VERBOSE=true vitest run __tests__/mcp/",
18
+ "test:mcp:integration": "pnpm run build && GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) VERBOSE=1 vitest run __tests__/mcp/mcp.integration.test.ts",
18
19
  "test:unit": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run --exclude='**/__tests__/integration.test.ts' --exclude='**/__tests__/mcp/**'",
19
20
  "test:integration": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest run __tests__/integration.test.ts",
20
21
  "test:integration:watch": "GIT_PROXY_HOST=http://tinyproxy:8888 TOKEN=$(../../scripts/login_auth0.sh) vitest watch run __tests__/integration.test.ts",
@@ -34,7 +35,8 @@
34
35
  "dev:mcp": "pnpm run build && node dist/index.mjs mcp",
35
36
  "debug:mcp": "pnpm run build && node dist/index.mjs mcp --debug",
36
37
  "generate": "pnpm run env -- graphql-codegen -r dotenv/config --config client_codegen.ts",
37
- "test:e2e": "cd ./__e2e__ && npm i && npm run test"
38
+ "test:e2e": "cd ./__e2e__ && npm i && npm run test && npm run test:mcp",
39
+ "test:e2e:mcp": "cd ./__e2e__ && npm i && npm run test:mcp"
38
40
  },
39
41
  "bin": {
40
42
  "mobbdev": "bin/cli.mjs"
@@ -47,7 +49,6 @@
47
49
  "@modelcontextprotocol/sdk": "1.12.1",
48
50
  "@octokit/core": "5.2.0",
49
51
  "@octokit/request-error": "5.1.1",
50
- "@types/libsodium-wrappers": "0.7.14",
51
52
  "adm-zip": "0.5.16",
52
53
  "axios": "1.9.0",
53
54
  "azure-devops-node-api": "12.1.0",
@@ -118,11 +119,11 @@
118
119
  "eslint-plugin-simple-import-sort": "10.0.0",
119
120
  "msw": "2.8.5",
120
121
  "nock": "14.0.5",
121
- "pino-pretty": "13.0.0",
122
122
  "prettier": "3.5.3",
123
123
  "tsup": "8.5.0",
124
124
  "typescript": "4.9.5",
125
- "vitest": "3.2.3"
125
+ "vitest": "3.2.3",
126
+ "@types/libsodium-wrappers": "0.7.14"
126
127
  },
127
128
  "engines": {
128
129
  "node": ">=18.20.0"