mobbdev 1.0.112 → 1.0.114

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 +678 -426
  2. package/package.json +10 -7
package/dist/index.mjs CHANGED
@@ -364,6 +364,7 @@ var IssueType_Enum = /* @__PURE__ */ ((IssueType_Enum2) => {
364
364
  IssueType_Enum2["AutoEscapeFalse"] = "AUTO_ESCAPE_FALSE";
365
365
  IssueType_Enum2["AvoidBuiltinShadowing"] = "AVOID_BUILTIN_SHADOWING";
366
366
  IssueType_Enum2["AvoidIdentityComparisonCachedTypes"] = "AVOID_IDENTITY_COMPARISON_CACHED_TYPES";
367
+ IssueType_Enum2["BufferOverflow"] = "BUFFER_OVERFLOW";
367
368
  IssueType_Enum2["ClientDomStoredCodeInjection"] = "CLIENT_DOM_STORED_CODE_INJECTION";
368
369
  IssueType_Enum2["CmDi"] = "CMDi";
369
370
  IssueType_Enum2["CmDiRelativePathCommand"] = "CMDi_relative_path_command";
@@ -533,6 +534,8 @@ var FixDetailsFragmentDoc = `
533
534
  confidence
534
535
  safeIssueType
535
536
  severityText
537
+ gitBlameLogin
538
+ severityValue
536
539
  vulnerabilityReportIssues {
537
540
  parsedIssueType
538
541
  parsedSeverity
@@ -596,7 +599,15 @@ var FixReportSummaryFieldsFragmentDoc = `
596
599
  }
597
600
  }
598
601
  fixes(
599
- where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
602
+ where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, {_or: [{gitBlameLogin: {_is_null: true}}, {_not: {gitBlameLogin: {_ilike: $currentUserEmail}}}]}, $filters]}
603
+ order_by: {severityValue: desc}
604
+ limit: $limit
605
+ offset: $offset
606
+ ) {
607
+ ...FixDetails
608
+ }
609
+ userFixes: fixes(
610
+ where: {_and: [{gitBlameLogin: {_ilike: $currentUserEmail}}, {vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
600
611
  order_by: {severityValue: desc}
601
612
  limit: $limit
602
613
  offset: $offset
@@ -662,18 +673,18 @@ var MeDocument = `
662
673
  }
663
674
  }
664
675
  `;
665
- var GetOrgAndProjectIdDocument = `
666
- query getOrgAndProjectId($filters: organization_to_organization_role_bool_exp, $limit: Int) {
667
- organization_to_organization_role(
668
- where: $filters
669
- order_by: {organization: {createdOn: desc}}
670
- limit: $limit
671
- ) {
672
- organization {
676
+ var GetLastOrgAndNamedProjectDocument = `
677
+ query getLastOrgAndNamedProject($email: String!, $projectName: String!) {
678
+ user(where: {email: {_eq: $email}}) {
679
+ id
680
+ userOrganizationsAndUserOrganizationRoles(order_by: {createdOn: desc}) {
673
681
  id
674
- projects(order_by: {updatedAt: desc}) {
682
+ organization {
675
683
  id
676
- name
684
+ projects(where: {name: {_eq: $projectName}}) {
685
+ name
686
+ id
687
+ }
677
688
  }
678
689
  }
679
690
  }
@@ -1064,32 +1075,32 @@ var AutoPrAnalysisDocument = `
1064
1075
  }
1065
1076
  }
1066
1077
  `;
1067
- var GetLatestReportByRepoUrlDocument = `
1068
- query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!) {
1069
- fixReport(
1070
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
1071
- order_by: {createdOn: desc}
1072
- limit: 1
1073
- ) {
1078
+ var GetReportFixesDocument = `
1079
+ query GetReportFixes($reportId: uuid!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
1080
+ fixReport(where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Finished}}]}) {
1074
1081
  ...FixReportSummaryFields
1075
1082
  }
1076
1083
  expiredReport: fixReport(
1077
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
1078
- order_by: {createdOn: desc}
1079
- limit: 1
1084
+ where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Expired}}]}
1080
1085
  ) {
1081
1086
  id
1082
1087
  expirationOn
1083
1088
  }
1084
1089
  }
1085
1090
  ${FixReportSummaryFieldsFragmentDoc}`;
1086
- var GetReportFixesDocument = `
1087
- query GetReportFixes($reportId: uuid!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!) {
1088
- fixReport(where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Finished}}]}) {
1091
+ var GetLatestReportByRepoUrlDocument = `
1092
+ query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
1093
+ fixReport(
1094
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
1095
+ order_by: {createdOn: desc}
1096
+ limit: 1
1097
+ ) {
1089
1098
  ...FixReportSummaryFields
1090
1099
  }
1091
1100
  expiredReport: fixReport(
1092
- where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Expired}}]}
1101
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
1102
+ order_by: {createdOn: desc}
1103
+ limit: 1
1093
1104
  ) {
1094
1105
  id
1095
1106
  expirationOn
@@ -1109,8 +1120,8 @@ function getSdk(client, withWrapper = defaultWrapper) {
1109
1120
  Me(variables, requestHeaders, signal) {
1110
1121
  return withWrapper((wrappedRequestHeaders) => client.request({ document: MeDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "Me", "query", variables);
1111
1122
  },
1112
- getOrgAndProjectId(variables, requestHeaders, signal) {
1113
- return withWrapper((wrappedRequestHeaders) => client.request({ document: GetOrgAndProjectIdDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getOrgAndProjectId", "query", variables);
1123
+ getLastOrgAndNamedProject(variables, requestHeaders, signal) {
1124
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLastOrgAndNamedProjectDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getLastOrgAndNamedProject", "query", variables);
1114
1125
  },
1115
1126
  GetEncryptedApiToken(variables, requestHeaders, signal) {
1116
1127
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetEncryptedApiTokenDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetEncryptedApiToken", "query", variables);
@@ -1169,12 +1180,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
1169
1180
  autoPrAnalysis(variables, requestHeaders, signal) {
1170
1181
  return withWrapper((wrappedRequestHeaders) => client.request({ document: AutoPrAnalysisDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "autoPrAnalysis", "mutation", variables);
1171
1182
  },
1172
- GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
1173
- return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
1174
- },
1175
1183
  GetReportFixes(variables, requestHeaders, signal) {
1176
1184
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetReportFixesDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetReportFixes", "query", variables);
1177
1185
  },
1186
+ GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
1187
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
1188
+ },
1178
1189
  updateDownloadedFixData(variables, requestHeaders, signal) {
1179
1190
  return withWrapper((wrappedRequestHeaders) => client.request({ document: UpdateDownloadedFixDataDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "updateDownloadedFixData", "mutation", variables);
1180
1191
  }
@@ -1444,7 +1455,11 @@ var fixDetailsData = {
1444
1455
  ["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: void 0,
1445
1456
  ["NO_NESTED_TRY" /* NoNestedTry */]: void 0,
1446
1457
  ["REDOS" /* Redos */]: void 0,
1447
- ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0
1458
+ ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0,
1459
+ ["BUFFER_OVERFLOW" /* BufferOverflow */]: {
1460
+ issueDescription: "Buffer Overflow occurs when a program writes data beyond the allocated memory space, leading to unexpected behavior or security vulnerabilities.",
1461
+ fixInstructions: "Implement proper input validation and bounds checking to prevent buffer overflows. Use safe string manipulation functions and ensure that the buffer size is properly managed."
1462
+ }
1448
1463
  };
1449
1464
 
1450
1465
  // src/features/analysis/scm/shared/src/getIssueType.ts
@@ -1558,7 +1573,8 @@ var issueTypeMap = {
1558
1573
  ["NO_NESTED_TRY" /* NoNestedTry */]: "No Nested Try",
1559
1574
  ["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: "Unnecessary Imports",
1560
1575
  ["REDOS" /* Redos */]: "Regular Expression Denial of Service",
1561
- ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception"
1576
+ ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception",
1577
+ ["BUFFER_OVERFLOW" /* BufferOverflow */]: "Buffer Overflow"
1562
1578
  };
1563
1579
  var issueTypeZ = z.nativeEnum(IssueType_Enum);
1564
1580
  var getIssueTypeFriendlyString = (issueType) => {
@@ -5141,14 +5157,17 @@ import { simpleGit } from "simple-git";
5141
5157
  // src/mcp/core/configs.ts
5142
5158
  var MCP_DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
5143
5159
  var MCP_API_KEY_HEADER_NAME = "x-mobb-key";
5144
- var MCP_LOGIN_MAX_WAIT = 10 * 60 * 1e3;
5145
- var MCP_LOGIN_CHECK_DELAY = 1 * 1e3;
5160
+ var MCP_LOGIN_MAX_WAIT = 2 * 60 * 1e3;
5161
+ var MCP_LOGIN_CHECK_DELAY = 2 * 1e3;
5146
5162
  var MCP_VUL_REPORT_DIGEST_TIMEOUT_MS = 5 * 60 * 1e3;
5147
5163
  var MCP_MAX_FILE_SIZE = MAX_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
5148
5164
  var MCP_PERIODIC_CHECK_INTERVAL = 15 * 60 * 1e3;
5149
5165
  var MCP_DEFAULT_MAX_FILES_TO_SCAN = 10;
5150
5166
  var MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
5151
5167
  var MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
5168
+ var MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES = "check_for_new_available_fixes";
5169
+ var MCP_TOOL_FETCH_AVAILABLE_FIXES = "fetch_available_fixes";
5170
+ var MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES = "scan_and_fix_vulnerabilities";
5152
5171
 
5153
5172
  // src/features/analysis/scm/FileUtils.ts
5154
5173
  import fs2 from "fs";
@@ -8653,12 +8672,6 @@ var GqlClientError = class extends Error {
8653
8672
  this.name = "GqlClientError";
8654
8673
  }
8655
8674
  };
8656
- var FileProcessingError = class extends Error {
8657
- constructor(message) {
8658
- super(message);
8659
- this.name = "FileProcessingError";
8660
- }
8661
- };
8662
8675
  var ReportInitializationError = class extends Error {
8663
8676
  constructor(message) {
8664
8677
  super(message);
@@ -9592,7 +9605,7 @@ var GQLClient = class {
9592
9605
  });
9593
9606
  return res.insert_cli_login_one?.id || "";
9594
9607
  }
9595
- async verifyConnection() {
9608
+ async verifyApiConnection() {
9596
9609
  try {
9597
9610
  await this.getUserInfo();
9598
9611
  } catch (e) {
@@ -9603,7 +9616,7 @@ var GQLClient = class {
9603
9616
  }
9604
9617
  return true;
9605
9618
  }
9606
- async verifyToken() {
9619
+ async validateUserToken() {
9607
9620
  await this.createCommunityUser();
9608
9621
  let info;
9609
9622
  try {
@@ -9614,31 +9627,46 @@ var GQLClient = class {
9614
9627
  }
9615
9628
  return info?.email || true;
9616
9629
  }
9617
- async getOrgAndProjectId(params = {}) {
9630
+ async getLastOrgAndNamedProject(params) {
9631
+ const me = await this.getUserInfo();
9632
+ const email = me?.email;
9633
+ if (!email) {
9634
+ throw new Error("User email not found");
9635
+ }
9618
9636
  const { projectName, userDefinedOrganizationId } = params;
9619
- const getOrgAndProjectIdResult = await this._clientSdk.getOrgAndProjectId({
9620
- filters: userDefinedOrganizationId ? { organizationId: { _eq: userDefinedOrganizationId } } : {},
9621
- limit: 1
9637
+ if (!projectName) {
9638
+ throw new Error("Project name is required");
9639
+ }
9640
+ const orgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
9641
+ email,
9642
+ projectName
9622
9643
  });
9623
- const [organizationToOrganizationRole] = getOrgAndProjectIdResult.organization_to_organization_role;
9624
- if (!organizationToOrganizationRole) {
9625
- throw new Error("Organization not found");
9644
+ if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
9645
+ throw new Error(
9646
+ `The user with email:${email} is not associated with any organization`
9647
+ );
9648
+ }
9649
+ const organization = orgAndProjectRes.user?.at(0)?.userOrganizationsAndUserOrganizationRoles.map((org) => org.organization).filter(
9650
+ (org) => userDefinedOrganizationId ? org.id === userDefinedOrganizationId : true
9651
+ )?.at(0);
9652
+ if (!organization) {
9653
+ throw new Error(
9654
+ `Organization with id:${userDefinedOrganizationId} not found`
9655
+ );
9626
9656
  }
9627
- const { organization: org } = organizationToOrganizationRole;
9628
- const project = projectName ? org?.projects.find((project2) => project2.name === projectName) ?? null : org?.projects[0];
9629
- let projectId = project?.id;
9657
+ let projectId = organization?.projects?.[0]?.id;
9630
9658
  if (!projectId) {
9631
9659
  const createdProject = await this._clientSdk.CreateProject({
9632
- organizationId: org.id,
9660
+ organizationId: organization.id,
9633
9661
  projectName: projectName || "My project"
9634
9662
  });
9635
9663
  projectId = createdProject.createProject.projectId;
9636
9664
  }
9637
- if (!project?.id) {
9665
+ if (!projectId) {
9638
9666
  throw new Error("Project not found");
9639
9667
  }
9640
9668
  return {
9641
- organizationId: org.id,
9669
+ organizationId: organization.id,
9642
9670
  projectId
9643
9671
  };
9644
9672
  }
@@ -10551,7 +10579,10 @@ async function _scan(params, { skipPrompts = false } = {}) {
10551
10579
  skipPrompts,
10552
10580
  apiKey
10553
10581
  });
10554
- const { projectId, organizationId } = await gqlClient.getOrgAndProjectId({
10582
+ if (!mobbProjectName) {
10583
+ throw new Error("mobbProjectName is required");
10584
+ }
10585
+ const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
10555
10586
  projectName: mobbProjectName,
10556
10587
  userDefinedOrganizationId: userOrganizationId
10557
10588
  });
@@ -11128,7 +11159,7 @@ async function handleMobbLogin({
11128
11159
  skipPrompts
11129
11160
  }) {
11130
11161
  const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
11131
- const isConnected = await inGqlClient.verifyConnection();
11162
+ const isConnected = await inGqlClient.verifyApiConnection();
11132
11163
  if (!isConnected) {
11133
11164
  createSpinner5().start().error({
11134
11165
  text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
@@ -11140,7 +11171,7 @@ async function handleMobbLogin({
11140
11171
  createSpinner5().start().success({
11141
11172
  text: `\u{1F513} Connection to Mobb: succeeded`
11142
11173
  });
11143
- const userVerify = await inGqlClient.verifyToken();
11174
+ const userVerify = await inGqlClient.validateUserToken();
11144
11175
  if (userVerify) {
11145
11176
  createSpinner5().start().success({
11146
11177
  text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
@@ -11194,7 +11225,7 @@ async function handleMobbLogin({
11194
11225
  throw new CliError();
11195
11226
  }
11196
11227
  const newGqlClient = new GQLClient({ apiKey: newApiToken, type: "apiKey" });
11197
- const loginSuccess = await newGqlClient.verifyToken();
11228
+ const loginSuccess = await newGqlClient.validateUserToken();
11198
11229
  if (loginSuccess) {
11199
11230
  debug18(`set api token ${newApiToken}`);
11200
11231
  config3.set("apiToken", newApiToken);
@@ -11257,7 +11288,7 @@ function validateRepoUrl(args) {
11257
11288
  });
11258
11289
  }
11259
11290
  }
11260
- var supportExtensions = [".json", ".xml", ".fpr", ".sarif"];
11291
+ var supportExtensions = [".json", ".xml", ".fpr", ".sarif", ".zip"];
11261
11292
  function validateReportFileFormat(reportFile) {
11262
11293
  if (!supportExtensions.includes(path10.extname(reportFile))) {
11263
11294
  throw new CliError(
@@ -11353,7 +11384,7 @@ import {
11353
11384
  } from "@modelcontextprotocol/sdk/types.js";
11354
11385
 
11355
11386
  // src/mcp/Logger.ts
11356
- var logglerUrl = "http://localhost:4444/log";
11387
+ var loggerUrl = "http://localhost:4444/log";
11357
11388
  var isTestEnvironment = process.env["VITEST"] || process.env["TEST"];
11358
11389
  var CIRCUIT_BREAKER_TIME = 5e3;
11359
11390
  var URL_CHECK_TIMEOUT = 200;
@@ -11400,11 +11431,14 @@ var Logger = class {
11400
11431
  this.isProcessing = false;
11401
11432
  return;
11402
11433
  }
11403
- const isReachable = await this.isUrlReachable(logglerUrl);
11434
+ const isReachable = await this.isUrlReachable(loggerUrl);
11404
11435
  if (!isReachable) {
11405
11436
  this.triggerCircuitBreaker();
11406
11437
  return;
11407
11438
  }
11439
+ await this.sendLogEntry(logEntry);
11440
+ }
11441
+ async sendLogEntry(logEntry) {
11408
11442
  const logMessage = {
11409
11443
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11410
11444
  level: logEntry.level,
@@ -11416,21 +11450,23 @@ var Logger = class {
11416
11450
  const timeoutId = setTimeout(() => {
11417
11451
  controller.abort();
11418
11452
  }, 500);
11419
- fetch(logglerUrl, {
11420
- method: "POST",
11421
- headers: { "Content-Type": "application/json" },
11422
- body: JSON.stringify(logMessage),
11423
- redirect: "error",
11424
- // do not follow redirects
11425
- signal: controller.signal
11426
- }).then((_response) => {
11453
+ try {
11454
+ await fetch(loggerUrl, {
11455
+ method: "POST",
11456
+ headers: { "Content-Type": "application/json" },
11457
+ body: JSON.stringify(logMessage),
11458
+ redirect: "error",
11459
+ // do not follow redirects
11460
+ signal: controller.signal
11461
+ });
11427
11462
  this.queue.shift();
11428
11463
  setTimeout(() => this.processQueue(), 0);
11429
- }).catch(() => {
11464
+ } catch (error) {
11430
11465
  this.triggerCircuitBreaker();
11431
- }).finally(() => {
11466
+ logError("Failed to send log entry", error);
11467
+ } finally {
11432
11468
  clearTimeout(timeoutId);
11433
- });
11469
+ }
11434
11470
  }
11435
11471
  triggerCircuitBreaker() {
11436
11472
  this.isCircuitBroken = true;
@@ -11456,12 +11492,90 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
11456
11492
  var log = logger.log.bind(logger);
11457
11493
 
11458
11494
  // src/mcp/services/McpGQLClient.ts
11459
- import crypto2 from "crypto";
11460
- import os2 from "os";
11461
11495
  import Configstore3 from "configstore";
11496
+ import crypto3 from "crypto";
11462
11497
  import { GraphQLClient as GraphQLClient2 } from "graphql-request";
11463
- import open4 from "open";
11464
11498
  import { v4 as uuidv42 } from "uuid";
11499
+
11500
+ // src/mcp/services/McpAuthService.ts
11501
+ import crypto2 from "crypto";
11502
+ import os2 from "os";
11503
+ import open4 from "open";
11504
+ var McpAuthService = class {
11505
+ constructor(client) {
11506
+ __publicField(this, "client");
11507
+ __publicField(this, "lastBrowserOpenTime", 0);
11508
+ this.client = client;
11509
+ }
11510
+ /**
11511
+ * Opens a browser window for authentication
11512
+ * @param url URL to open in browser
11513
+ * @param isBackgoundCall Whether this is called from tools context
11514
+ */
11515
+ async openBrowser(url, isBackgoundCall) {
11516
+ if (isBackgoundCall) {
11517
+ const now = Date.now();
11518
+ if (now - this.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
11519
+ logDebug(`browser cooldown active, skipping open for ${url}`);
11520
+ return;
11521
+ }
11522
+ }
11523
+ logDebug(`opening browser url ${url}`);
11524
+ await open4(url);
11525
+ this.lastBrowserOpenTime = Date.now();
11526
+ }
11527
+ /**
11528
+ * Handles the complete authentication flow
11529
+ * @param isBackgoundCall Whether this is called from tools context
11530
+ * @returns Authenticated API token
11531
+ */
11532
+ async authenticate(isBackgoundCall = false) {
11533
+ const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
11534
+ modulusLength: 2048
11535
+ });
11536
+ logDebug("creating cli login");
11537
+ const loginId = await this.client.createCliLogin({
11538
+ publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
11539
+ });
11540
+ if (!loginId) {
11541
+ throw new CliLoginError("Error: createCliLogin failed");
11542
+ }
11543
+ logDebug(`cli login created ${loginId}`);
11544
+ const webLoginUrl2 = `${WEB_APP_URL}/cli-login`;
11545
+ const browserUrl = `${webLoginUrl2}/${loginId}?hostname=${os2.hostname()}`;
11546
+ await this.openBrowser(browserUrl, isBackgoundCall);
11547
+ logDebug(`waiting for login to complete`);
11548
+ let newApiToken = null;
11549
+ for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
11550
+ const encryptedApiToken = await this.client.getEncryptedApiToken({
11551
+ loginId
11552
+ });
11553
+ if (encryptedApiToken) {
11554
+ logDebug("encrypted API token received");
11555
+ newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
11556
+ logDebug("API token decrypted");
11557
+ break;
11558
+ }
11559
+ await sleep(MCP_LOGIN_CHECK_DELAY);
11560
+ }
11561
+ if (!newApiToken) {
11562
+ throw new FailedToGetApiTokenError(
11563
+ "Error: failed to get encrypted api token"
11564
+ );
11565
+ }
11566
+ const verifyClient = new McpGQLClient({
11567
+ apiKey: newApiToken,
11568
+ type: "apiKey"
11569
+ });
11570
+ const loginSuccess = await verifyClient.validateUserToken();
11571
+ if (!loginSuccess) {
11572
+ throw new AuthenticationError("Invalid API token");
11573
+ }
11574
+ return newApiToken;
11575
+ }
11576
+ };
11577
+
11578
+ // src/mcp/services/McpGQLClient.ts
11465
11579
  var mobbConfigStore = new Configstore3(packageJson.name, { apiToken: "" });
11466
11580
  var McpGQLClient = class {
11467
11581
  constructor(args) {
@@ -11498,17 +11612,17 @@ var McpGQLClient = class {
11498
11612
  }
11499
11613
  };
11500
11614
  }
11501
- async verifyConnection() {
11615
+ async verifyApiConnection() {
11502
11616
  try {
11503
- logDebug("GraphQL: Calling Me query for connection verification");
11617
+ logDebug("GraphQL: Calling Me query for API connection verification");
11504
11618
  const result = await this.clientSdk.Me();
11505
11619
  logDebug("GraphQL: Me query successful", { result });
11506
11620
  return true;
11507
11621
  } catch (e) {
11508
11622
  const error = e;
11509
- logDebug(`verify connection failed ${error.toString()}`);
11623
+ logDebug(`API connection verification failed ${error.toString()}`);
11510
11624
  if (error?.toString().includes("FetchError")) {
11511
- logError("verify connection failed", { error });
11625
+ logError("API connection verification failed", { error });
11512
11626
  return false;
11513
11627
  }
11514
11628
  }
@@ -11520,7 +11634,7 @@ var McpGQLClient = class {
11520
11634
  const result = await this.clientSdk.uploadS3BucketInfo({
11521
11635
  fileName: "report.json"
11522
11636
  });
11523
- logInfo("GraphQL: uploadS3BucketInfo successful", { result });
11637
+ logDebug("GraphQL: uploadS3BucketInfo successful", { result });
11524
11638
  return result;
11525
11639
  } catch (e) {
11526
11640
  logError("GraphQL: uploadS3BucketInfo failed", {
@@ -11536,7 +11650,7 @@ var McpGQLClient = class {
11536
11650
  const res = await this.clientSdk.getAnalysis({
11537
11651
  analysisId
11538
11652
  });
11539
- logInfo("GraphQL: getAnalysis successful", { result: res });
11653
+ logDebug("GraphQL: getAnalysis successful", { result: res });
11540
11654
  if (!res.analysis) {
11541
11655
  throw new Error(`Analysis not found: ${analysisId}`);
11542
11656
  }
@@ -11556,7 +11670,7 @@ var McpGQLClient = class {
11556
11670
  variables
11557
11671
  });
11558
11672
  const result = await this.clientSdk.SubmitVulnerabilityReport(variables);
11559
- logInfo("GraphQL: SubmitVulnerabilityReport successful", { result });
11673
+ logDebug("GraphQL: SubmitVulnerabilityReport successful", { result });
11560
11674
  return result;
11561
11675
  } catch (e) {
11562
11676
  logError("GraphQL: SubmitVulnerabilityReport failed", {
@@ -11590,7 +11704,7 @@ var McpGQLClient = class {
11590
11704
  return;
11591
11705
  }
11592
11706
  if (callbackStates.includes(data.analysis?.state)) {
11593
- logInfo("GraphQL: Analysis state matches callback states", {
11707
+ logDebug("GraphQL: Analysis state matches callback states", {
11594
11708
  analysisId: data.analysis.id,
11595
11709
  state: data.analysis.state,
11596
11710
  callbackStates
@@ -11609,7 +11723,7 @@ var McpGQLClient = class {
11609
11723
  timeoutInMs: params.timeoutInMs
11610
11724
  }
11611
11725
  );
11612
- logInfo("GraphQL: GetAnalysis subscription completed", { result });
11726
+ logDebug("GraphQL: GetAnalysis subscription completed", { result });
11613
11727
  return result;
11614
11728
  } catch (e) {
11615
11729
  logError("GraphQL: GetAnalysis subscription failed", {
@@ -11630,38 +11744,41 @@ var McpGQLClient = class {
11630
11744
  if (!userEmail) {
11631
11745
  throw new Error("User email not found");
11632
11746
  }
11633
- const shortEmailHash = crypto2.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
11747
+ const shortEmailHash = crypto3.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
11634
11748
  const projectName = `MCP Scans ${shortEmailHash}`;
11635
- logDebug("GraphQL: Calling getOrgAndProjectId query", { projectName });
11636
- const getOrgAndProjectIdResult = await this.clientSdk.getOrgAndProjectId({
11637
- filters: {},
11638
- limit: 1
11749
+ logDebug("GraphQL: Calling getLastOrgAndNamedProject query", {
11750
+ projectName
11639
11751
  });
11640
- logInfo("GraphQL: getOrgAndProjectId successful", {
11641
- result: getOrgAndProjectIdResult
11752
+ const orgAndProjectRes = await this.clientSdk.getLastOrgAndNamedProject({
11753
+ email: userEmail,
11754
+ projectName
11755
+ });
11756
+ logDebug("GraphQL: getLastOrgAndNamedProject successful", {
11757
+ result: orgAndProjectRes
11642
11758
  });
11643
- const [organizationToOrganizationRole] = getOrgAndProjectIdResult.organization_to_organization_role;
11644
- if (!organizationToOrganizationRole) {
11645
- throw new Error("Organization not found");
11646
- }
11647
- const { organization: org } = organizationToOrganizationRole;
11648
- const project = projectName ? org?.projects.find((project2) => project2.name === projectName) ?? null : org?.projects[0];
11649
- if (project?.id) {
11650
- logInfo("GraphQL: Found existing project", {
11651
- projectId: project.id,
11759
+ if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
11760
+ throw new Error(
11761
+ `The user with email:${userEmail} is not associated with any organization`
11762
+ );
11763
+ }
11764
+ const organization = orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization;
11765
+ const projectId = organization?.projects?.[0]?.id;
11766
+ if (projectId) {
11767
+ logDebug("GraphQL: Found existing project", {
11768
+ projectId,
11652
11769
  projectName
11653
11770
  });
11654
- return project.id;
11771
+ return projectId;
11655
11772
  }
11656
11773
  logDebug("GraphQL: Project not found, creating new project", {
11657
- organizationId: org.id,
11774
+ organizationId: organization.id,
11658
11775
  projectName
11659
11776
  });
11660
11777
  const createdProject = await this.clientSdk.CreateProject({
11661
- organizationId: org.id,
11778
+ organizationId: organization.id,
11662
11779
  projectName
11663
11780
  });
11664
- logInfo("GraphQL: CreateProject successful", { result: createdProject });
11781
+ logDebug("GraphQL: CreateProject successful", { result: createdProject });
11665
11782
  return createdProject.createProject.projectId;
11666
11783
  } catch (e) {
11667
11784
  logError("GraphQL: getProjectId failed", {
@@ -11675,15 +11792,15 @@ var McpGQLClient = class {
11675
11792
  const { me } = await this.clientSdk.Me();
11676
11793
  return me;
11677
11794
  }
11678
- async verifyToken() {
11679
- logDebug("verifying token");
11795
+ async validateUserToken() {
11796
+ logDebug("validating user token");
11680
11797
  try {
11681
11798
  await this.clientSdk.CreateCommunityUser();
11682
11799
  const info = await this.getUserInfo();
11683
- logDebug("token verified");
11800
+ logDebug("user token validated successfully");
11684
11801
  return info?.email || true;
11685
11802
  } catch (e) {
11686
- logError("verify token failed");
11803
+ logError("user token validation failed");
11687
11804
  return false;
11688
11805
  }
11689
11806
  }
@@ -11716,18 +11833,34 @@ var McpGQLClient = class {
11716
11833
  return null;
11717
11834
  }
11718
11835
  }
11719
- async _updateFixesArchiveState(fixIds) {
11836
+ mergeUserAndSystemFixes(reportData, limit) {
11837
+ if (!reportData) return [];
11838
+ const { userFixes = [], fixes = [] } = reportData;
11839
+ const fixMap = /* @__PURE__ */ new Map();
11840
+ for (const fix of userFixes) {
11841
+ if (fix.id) {
11842
+ fixMap.set(fix.id, fix);
11843
+ }
11844
+ }
11845
+ for (const fix of fixes) {
11846
+ if (fix.id && !fixMap.has(fix.id)) {
11847
+ fixMap.set(fix.id, fix);
11848
+ }
11849
+ }
11850
+ return Array.from(fixMap.values()).slice(0, limit);
11851
+ }
11852
+ async updateFixesDownloadStatus(fixIds) {
11720
11853
  if (fixIds.length > 0) {
11721
11854
  const resUpdate = await this.clientSdk.updateDownloadedFixData({
11722
11855
  fixIds,
11723
11856
  source: "MCP" /* Mcp */
11724
11857
  });
11725
- logInfo("GraphQL: updateFixesArchiveState successful", {
11858
+ logDebug("GraphQL: updateFixesDownloadStatus successful", {
11726
11859
  result: resUpdate,
11727
11860
  fixIds
11728
11861
  });
11729
11862
  } else {
11730
- logInfo("GraphQL: No fixes found");
11863
+ logDebug("GraphQL: No fixes found to update download status");
11731
11864
  }
11732
11865
  }
11733
11866
  async getLatestReportByRepoUrl({
@@ -11741,19 +11874,35 @@ var McpGQLClient = class {
11741
11874
  limit,
11742
11875
  offset
11743
11876
  });
11877
+ let currentUserEmail = "%@%";
11878
+ try {
11879
+ const userInfo = await this.getUserInfo();
11880
+ if (userInfo?.email) {
11881
+ currentUserEmail = `%${userInfo.email}%`;
11882
+ }
11883
+ } catch (err) {
11884
+ logDebug("Failed to get user email, using default pattern", {
11885
+ error: err
11886
+ });
11887
+ }
11744
11888
  const res = await this.clientSdk.GetLatestReportByRepoUrl({
11745
11889
  repoUrl,
11746
11890
  limit,
11747
- offset
11891
+ offset,
11892
+ currentUserEmail
11748
11893
  });
11749
- logInfo("GraphQL: GetLatestReportByRepoUrl successful", {
11894
+ logDebug("GraphQL: GetLatestReportByRepoUrl successful", {
11750
11895
  result: res,
11751
11896
  reportCount: res.fixReport?.length || 0
11752
11897
  });
11753
- const fixIds = res.fixReport?.[0]?.fixes?.map((fix) => fix.id) || [];
11754
- await this._updateFixesArchiveState(fixIds);
11898
+ const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
11899
+ const fixIds = fixes.map((fix) => fix.id);
11900
+ await this.updateFixesDownloadStatus(fixIds);
11755
11901
  return {
11756
- fixReport: res.fixReport?.[0] || null,
11902
+ fixReport: res.fixReport?.[0] ? {
11903
+ ...res.fixReport?.[0],
11904
+ fixes
11905
+ } : null,
11757
11906
  expiredReport: res.expiredReport?.[0] || null
11758
11907
  };
11759
11908
  } catch (e) {
@@ -11772,14 +11921,14 @@ var McpGQLClient = class {
11772
11921
  issueType,
11773
11922
  severity
11774
11923
  }) {
11924
+ const filters = {};
11925
+ if (issueType && issueType.length > 0) {
11926
+ filters["safeIssueType"] = { _in: issueType };
11927
+ }
11928
+ if (severity && severity.length > 0) {
11929
+ filters["severityText"] = { _in: severity };
11930
+ }
11775
11931
  try {
11776
- const filters = {};
11777
- if (issueType && issueType.length > 0) {
11778
- filters["safeIssueType"] = { _in: issueType };
11779
- }
11780
- if (severity && severity.length > 0) {
11781
- filters["severityText"] = { _in: severity };
11782
- }
11783
11932
  logDebug("GraphQL: Calling GetReportFixes query", {
11784
11933
  reportId,
11785
11934
  limit,
@@ -11788,13 +11937,25 @@ var McpGQLClient = class {
11788
11937
  issueType,
11789
11938
  severity
11790
11939
  });
11940
+ let currentUserEmail = "%@%";
11941
+ try {
11942
+ const userInfo = await this.getUserInfo();
11943
+ if (userInfo?.email) {
11944
+ currentUserEmail = `%${userInfo.email}%`;
11945
+ }
11946
+ } catch (err) {
11947
+ logDebug("Failed to get user email, using default pattern", {
11948
+ error: err
11949
+ });
11950
+ }
11791
11951
  const res = await this.clientSdk.GetReportFixes({
11792
11952
  reportId,
11793
11953
  limit,
11794
11954
  offset,
11795
- filters
11955
+ filters,
11956
+ currentUserEmail
11796
11957
  });
11797
- logInfo("GraphQL: GetReportFixes successful", {
11958
+ logDebug("GraphQL: GetReportFixes successful", {
11798
11959
  result: res,
11799
11960
  fixCount: res.fixReport?.[0]?.fixes?.length || 0,
11800
11961
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0
@@ -11802,10 +11963,11 @@ var McpGQLClient = class {
11802
11963
  if (res.fixReport.length === 0) {
11803
11964
  return null;
11804
11965
  }
11805
- const fixIds = res.fixReport?.[0]?.fixes?.map((fix) => fix.id) || [];
11806
- await this._updateFixesArchiveState(fixIds);
11966
+ const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
11967
+ const fixIds = fixes.map((fix) => fix.id);
11968
+ await this.updateFixesDownloadStatus(fixIds);
11807
11969
  return {
11808
- fixes: res.fixReport?.[0]?.fixes || [],
11970
+ fixes,
11809
11971
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
11810
11972
  expiredReport: res.expiredReport?.[0] || null
11811
11973
  };
@@ -11819,82 +11981,29 @@ var McpGQLClient = class {
11819
11981
  }
11820
11982
  }
11821
11983
  };
11822
- async function openBrowser(url, isToolsCall) {
11823
- if (isToolsCall) {
11824
- const now = Date.now();
11825
- const lastBrowserOpenTime = mobbConfigStore.get("lastBrowserOpenTime") || 0;
11826
- if (now - lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
11827
- logDebug(`browser cooldown active, skipping open for ${url}`);
11828
- return;
11829
- }
11830
- }
11831
- logDebug(`opening browser url ${url}`);
11832
- await open4(url);
11833
- mobbConfigStore.set("lastBrowserOpenTime", Date.now());
11834
- }
11835
- async function getMcpGQLClient({
11836
- isToolsCall = false
11984
+ async function createAuthenticatedMcpGQLClient({
11985
+ isBackgoundCall = false
11837
11986
  } = {}) {
11838
11987
  logDebug("getting config", { apiToken: mobbConfigStore.get("apiToken") });
11839
- const inGqlClient = new McpGQLClient({
11988
+ const initialClient = new McpGQLClient({
11840
11989
  apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
11841
11990
  mobbConfigStore.get("apiToken") || "",
11842
11991
  type: "apiKey"
11843
11992
  });
11844
- const isConnected = await inGqlClient.verifyConnection();
11845
- logDebug("isConnected", { isConnected });
11993
+ const isConnected = await initialClient.verifyApiConnection();
11994
+ logDebug("API connection status", { isConnected });
11846
11995
  if (!isConnected) {
11847
11996
  throw new ApiConnectionError("Error: failed to connect to Mobb API");
11848
11997
  }
11849
- logDebug("verifying token");
11850
- const userVerify = await inGqlClient.verifyToken();
11998
+ logDebug("validating user token");
11999
+ const userVerify = await initialClient.validateUserToken();
11851
12000
  if (userVerify) {
11852
- return inGqlClient;
12001
+ return initialClient;
11853
12002
  }
11854
- logDebug("verifying token failed");
11855
- const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
11856
- modulusLength: 2048
11857
- });
11858
- logDebug("creating cli login");
11859
- const loginId = await inGqlClient.createCliLogin({
11860
- publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
11861
- });
11862
- if (!loginId) {
11863
- throw new CliLoginError("Error: createCliLogin failed");
11864
- }
11865
- logDebug(`cli login created ${loginId}`);
11866
- const webLoginUrl2 = `${WEB_APP_URL}/cli-login`;
11867
- const browserUrl = `${webLoginUrl2}/${loginId}?hostname=${os2.hostname()}`;
11868
- logDebug(`opening browser url ${browserUrl}`);
11869
- await openBrowser(browserUrl, isToolsCall);
11870
- logDebug(`waiting for login to complete`);
11871
- let newApiToken = null;
11872
- for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
11873
- const encryptedApiToken = await inGqlClient.getEncryptedApiToken({
11874
- loginId
11875
- });
11876
- if (encryptedApiToken) {
11877
- logDebug("encrypted API token received");
11878
- newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
11879
- logDebug("API token decrypted");
11880
- break;
11881
- }
11882
- await sleep(MCP_LOGIN_CHECK_DELAY);
11883
- }
11884
- if (!newApiToken) {
11885
- throw new FailedToGetApiTokenError(
11886
- "Error: failed to get encrypted api token"
11887
- );
11888
- }
11889
- const newGqlClient = new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
11890
- const loginSuccess = await newGqlClient.verifyToken();
11891
- if (loginSuccess) {
11892
- logDebug(`set api token ${newApiToken}`);
11893
- mobbConfigStore.set("apiToken", newApiToken);
11894
- } else {
11895
- throw new AuthenticationError("Invalid API token");
11896
- }
11897
- return newGqlClient;
12003
+ const authService = new McpAuthService(initialClient);
12004
+ const newApiToken = await authService.authenticate(isBackgoundCall);
12005
+ mobbConfigStore.set("apiToken", newApiToken);
12006
+ return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
11898
12007
  }
11899
12008
 
11900
12009
  // src/mcp/core/ToolRegistry.ts
@@ -11951,14 +12060,11 @@ var McpServer = class {
11951
12060
  this.toolRegistry = new ToolRegistry();
11952
12061
  this.setupHandlers();
11953
12062
  this.setupProcessEventHandlers();
11954
- logInfo("MCP server instance created", config4);
12063
+ logInfo("MCP server instance created");
12064
+ logDebug("MCP server instance config", { config: config4 });
11955
12065
  }
11956
- setupProcessEventHandlers() {
11957
- if (this.isEventHandlersSetup) {
11958
- logDebug("Process event handlers already setup, skipping");
11959
- return;
11960
- }
11961
- const signals = {
12066
+ handleProcessSignal(signal, error) {
12067
+ const messages = {
11962
12068
  SIGINT: "MCP server interrupted",
11963
12069
  SIGTERM: "MCP server terminated",
11964
12070
  exit: "MCP server exiting",
@@ -11966,26 +12072,46 @@ var McpServer = class {
11966
12072
  unhandledRejection: "Unhandled promise rejection in MCP server",
11967
12073
  warning: "Warning in MCP server"
11968
12074
  };
11969
- Object.entries(signals).forEach(([signal, message]) => {
11970
- process.on(
11971
- signal,
11972
- (error) => {
11973
- if (error && signal !== "exit") {
11974
- logError(`${message}`, { error, signal });
11975
- } else {
11976
- logInfo(message, { signal });
11977
- }
11978
- if (signal === "SIGINT" || signal === "SIGTERM") {
11979
- process.exit(0);
11980
- }
11981
- if (signal === "uncaughtException") {
11982
- process.exit(1);
11983
- }
11984
- }
11985
- );
12075
+ const message = messages[signal] || `Unhandled signal: ${signal}`;
12076
+ if (signal === "exit") {
12077
+ const exitCode = error;
12078
+ if (exitCode === 0 || exitCode === void 0) {
12079
+ logDebug(`${message} (clean exit)`, { signal, exitCode });
12080
+ } else {
12081
+ logWarn(`${message} (exit code: ${exitCode})`, { signal, exitCode });
12082
+ }
12083
+ } else if (error) {
12084
+ logError(`${message}`, { error, signal });
12085
+ } else {
12086
+ logDebug(message, { signal });
12087
+ }
12088
+ if (signal === "SIGINT" || signal === "SIGTERM") {
12089
+ process.exit(0);
12090
+ }
12091
+ if (signal === "uncaughtException") {
12092
+ process.exit(1);
12093
+ }
12094
+ }
12095
+ setupProcessEventHandlers() {
12096
+ if (this.isEventHandlersSetup) {
12097
+ logDebug("Process event handlers already setup, skipping");
12098
+ return;
12099
+ }
12100
+ const signals = [
12101
+ "SIGINT",
12102
+ "SIGTERM",
12103
+ "exit",
12104
+ "uncaughtException",
12105
+ "unhandledRejection",
12106
+ "warning"
12107
+ ];
12108
+ signals.forEach((signal) => {
12109
+ process.on(signal, (error) => {
12110
+ this.handleProcessSignal(signal, error);
12111
+ });
11986
12112
  });
11987
12113
  this.isEventHandlersSetup = true;
11988
- logDebug("Process event handlers registered");
12114
+ logInfo("Process event handlers registered");
11989
12115
  }
11990
12116
  createShutdownPromise() {
11991
12117
  return new Promise((resolve) => {
@@ -11997,12 +12123,45 @@ var McpServer = class {
11997
12123
  process.once("SIGTERM", cleanup);
11998
12124
  });
11999
12125
  }
12126
+ async triggerScanForNewAvailableFixes() {
12127
+ const gqlClient = await createAuthenticatedMcpGQLClient({
12128
+ isBackgoundCall: true
12129
+ });
12130
+ const isConnected = await gqlClient.verifyApiConnection();
12131
+ if (!isConnected) {
12132
+ logError("Failed to connect to the API, skipping scan");
12133
+ return;
12134
+ }
12135
+ if (process.env["WORKSPACE_FOLDER_PATHS"]) {
12136
+ logDebug("WORKSPACE_FOLDER_PATHS is set", {
12137
+ WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
12138
+ });
12139
+ try {
12140
+ const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
12141
+ MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
12142
+ );
12143
+ logInfo("Triggering periodic scan for new available fixes");
12144
+ checkForNewAvailableFixesTool.triggerScan({
12145
+ path: process.env["WORKSPACE_FOLDER_PATHS"],
12146
+ gqlClient
12147
+ });
12148
+ } catch (error) {
12149
+ logError("Error getting workspace folder path tool", { error });
12150
+ }
12151
+ }
12152
+ }
12000
12153
  async handleListToolsRequest(request) {
12001
- logInfo("Received list_tools request", { params: request.params });
12002
- logInfo("Request", {
12154
+ logInfo("Received list_tools request");
12155
+ logDebug("list_tools request", {
12156
+ request: JSON.parse(JSON.stringify(request))
12157
+ });
12158
+ logDebug("Request", {
12003
12159
  request: JSON.parse(JSON.stringify(request))
12004
12160
  });
12005
- void getMcpGQLClient({ isToolsCall: true });
12161
+ logDebug("env", {
12162
+ env: process.env
12163
+ });
12164
+ void this.triggerScanForNewAvailableFixes();
12006
12165
  const toolsDefinitions = this.toolRegistry.getAllTools();
12007
12166
  const response = {
12008
12167
  tools: toolsDefinitions.map((tool) => ({
@@ -12016,13 +12175,13 @@ var McpServer = class {
12016
12175
  }
12017
12176
  }))
12018
12177
  };
12019
- logInfo("Returning list_tools response", { response });
12178
+ logDebug("Returning list_tools response", { response });
12020
12179
  return response;
12021
12180
  }
12022
12181
  async handleCallToolRequest(request) {
12023
12182
  const { name, arguments: args } = request.params;
12024
- logInfo(`Received call tool request for ${name}`, { name, args });
12025
- logInfo("Request", {
12183
+ logInfo(`Received call tool request for ${name}`);
12184
+ logDebug("Request", {
12026
12185
  request: JSON.parse(JSON.stringify(request))
12027
12186
  });
12028
12187
  try {
@@ -12035,10 +12194,11 @@ var McpServer = class {
12035
12194
  });
12036
12195
  throw new Error(errorMsg);
12037
12196
  }
12038
- logDebug(`Executing tool: ${name}`, { args });
12197
+ logInfo(`Executing tool: ${name}`);
12039
12198
  const response = await tool.execute(args);
12040
12199
  const serializedResponse = JSON.parse(JSON.stringify(response));
12041
- logInfo(`Tool ${name} executed successfully`, {
12200
+ logInfo(`Tool ${name} executed successfully`);
12201
+ logDebug(`Tool ${name} executed successfully`, {
12042
12202
  responseType: typeof response,
12043
12203
  hasContent: !!serializedResponse.content
12044
12204
  });
@@ -12062,18 +12222,18 @@ var McpServer = class {
12062
12222
  CallToolRequestSchema,
12063
12223
  (request) => this.handleCallToolRequest(request)
12064
12224
  );
12065
- logDebug("MCP server handlers registered");
12225
+ logInfo("MCP server handlers registered");
12066
12226
  }
12067
12227
  registerTool(tool) {
12068
12228
  this.toolRegistry.registerTool(tool);
12069
- logDebug(`Tool registered: ${tool.name}`);
12229
+ logInfo(`Tool registered: ${tool.name}`);
12070
12230
  }
12071
12231
  async start() {
12072
12232
  try {
12073
- logDebug("Starting MCP server");
12233
+ logInfo("Starting MCP server");
12074
12234
  const transport = new StdioServerTransport();
12075
12235
  await this.server.connect(transport);
12076
- logInfo("MCP server is running on stdin/stdout");
12236
+ logDebug("MCP server is running on stdin/stdout");
12077
12237
  process.stdin.resume();
12078
12238
  await this.createShutdownPromise();
12079
12239
  await this.stop();
@@ -12083,7 +12243,7 @@ var McpServer = class {
12083
12243
  }
12084
12244
  }
12085
12245
  async stop() {
12086
- logInfo("MCP server shutting down");
12246
+ logDebug("MCP server shutting down");
12087
12247
  }
12088
12248
  };
12089
12249
 
@@ -12168,14 +12328,15 @@ var BaseTool = class {
12168
12328
  };
12169
12329
  }
12170
12330
  async execute(args) {
12171
- logInfo(`Authenticating tool: ${this.name}`, { args });
12172
- const mcpGqlClient = await getMcpGQLClient();
12331
+ logDebug(`Authenticating tool: ${this.name}`, { args });
12332
+ const mcpGqlClient = await createAuthenticatedMcpGQLClient();
12173
12333
  const userInfo = await mcpGqlClient.getUserInfo();
12174
- logDebug("Authenticated", { userInfo });
12334
+ logDebug("User authenticated successfully", { userInfo });
12175
12335
  const validatedArgs = this.validateInput(args);
12176
12336
  logDebug(`Tool ${this.name} input validation successful`, {
12177
12337
  validatedArgs
12178
12338
  });
12339
+ logInfo(`Executing tool: ${this.name}`);
12179
12340
  const result = await this.executeInternal(validatedArgs);
12180
12341
  logInfo(`Tool ${this.name} executed successfully`);
12181
12342
  return result;
@@ -12209,7 +12370,7 @@ var BaseTool = class {
12209
12370
  };
12210
12371
 
12211
12372
  // src/mcp/core/prompts.ts
12212
- function frienlyType(s) {
12373
+ function friendlyType(s) {
12213
12374
  const withoutUnderscores = s.replace(/_/g, " ");
12214
12375
  const result = withoutUnderscores.replace(/([a-z])([A-Z])/g, "$1 $2");
12215
12376
  return result.charAt(0).toUpperCase() + result.slice(1);
@@ -12229,13 +12390,15 @@ var applyFixesPrompt = ({
12229
12390
  return noFixesReturnedForParameters;
12230
12391
  }
12231
12392
  const fixList = fixes.map((fix) => {
12232
- const vulnerabilityType = frienlyType(fix.safeIssueType);
12393
+ const vulnerabilityType = friendlyType(fix.safeIssueType);
12233
12394
  const vulnerabilityDescription = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.extraContext?.fixDescription : void 0;
12234
12395
  const patch = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.patch : void 0;
12396
+ const gitBlameLogin = fix.gitBlameLogin;
12235
12397
  return {
12236
12398
  vulnerabilityType,
12237
12399
  vulnerabilityDescription,
12238
- patch
12400
+ patch,
12401
+ gitBlameLogin
12239
12402
  };
12240
12403
  });
12241
12404
  return `## CRITICAL INSTRUCTIONS - READ CAREFULLY
@@ -12287,7 +12450,9 @@ ${fixList.map(
12287
12450
 
12288
12451
  **\u{1F4DD} Description:** ${fix.vulnerabilityDescription || "Security vulnerability fix"}
12289
12452
 
12290
- **\u{1F527} Action Required:** Apply the following patch exactly as shown
12453
+ ${fix.gitBlameLogin ? `**\u{1F464} Git Blame:** The code that needs to be fixed was last modified by: \`${fix.gitBlameLogin}\`
12454
+
12455
+ ` : ""}**\u{1F527} Action Required:** Apply the following patch exactly as shown
12291
12456
 
12292
12457
  **\u{1F4C1} Patch to Apply:**
12293
12458
  \`\`\`diff
@@ -12334,7 +12499,7 @@ We were unable to find a previous vulnerability report for this repository. This
12334
12499
 
12335
12500
  ### \u{1F3AF} Recommended Actions
12336
12501
  1. **Run a new security scan** to analyze your codebase
12337
- - Use the \`scan_and_fix_vulnerabilities\` tool to start a fresh scan
12502
+ - Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool to start a fresh scan
12338
12503
  - This will analyze your current code for security issues
12339
12504
 
12340
12505
  2. **Verify repository access**
@@ -12362,7 +12527,7 @@ Your most recent vulnerability report for this repository **expired on ${lastRep
12362
12527
 
12363
12528
  ### \u{1F3AF} Recommended Actions
12364
12529
  1. **Run a fresh security scan** to generate an up-to-date vulnerability report.
12365
- - Use the \`scan_and_fix_vulnerabilities\` tool.
12530
+ - Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool.
12366
12531
  2. **Verify repository access** if scans fail to run or the repository has moved.
12367
12532
  3. **Review your CI/CD pipeline** to ensure regular scans are triggered.
12368
12533
 
@@ -12416,7 +12581,7 @@ ${applyFixesPrompt({
12416
12581
  hasMore,
12417
12582
  nextOffset: 0,
12418
12583
  shownCount: fixReport.fixes.length,
12419
- currentTool: "fetch_available_fixes",
12584
+ currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
12420
12585
  offset
12421
12586
  })}`;
12422
12587
  };
@@ -12441,7 +12606,6 @@ To perform a **rescan** of your repository (fetching a brand-new vulnerability r
12441
12606
  This will start a new analysis, discard any cached results.
12442
12607
 
12443
12608
  \u26A0\uFE0F *Note:* A full rescan may take longer to complete than simply fetching additional fixes because your repository is re-uploaded and re-analyzed from scratch.
12444
-
12445
12609
  `;
12446
12610
  var noFixesFoundPrompt = ({
12447
12611
  scannedFiles
@@ -12467,20 +12631,19 @@ var fixesPrompt = ({
12467
12631
 
12468
12632
  ${applyFixesPrompt({
12469
12633
  fixes,
12470
- totalCount,
12471
12634
  hasMore,
12635
+ totalCount,
12472
12636
  nextOffset,
12473
12637
  shownCount,
12474
- currentTool: "scan_and_fix_vulnerabilities",
12638
+ currentTool: MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES,
12475
12639
  offset
12476
12640
  })}
12477
12641
 
12478
12642
  ${nextStepsPrompt({ scannedFiles })}
12479
12643
  `;
12480
12644
  };
12481
- var noFreshFixesPrompt = `No fresh fixes available for this repository at this time.
12482
- `;
12483
- var initialScanInProgressPrompt = `Initial scan in progress. Call the tool again in 1 minute to check for available fixes.`;
12645
+ var noFreshFixesPrompt = `No fresh fixes available for this repository at this time. No user message needed. Do not show any comments to the user.`;
12646
+ var initialScanInProgressPrompt = `Initial scan is still in progress. No user message needed. Do not show any comments to the user.`;
12484
12647
  var freshFixesPrompt = ({ fixes }) => {
12485
12648
  return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
12486
12649
 
@@ -12490,7 +12653,7 @@ ${applyFixesPrompt({
12490
12653
  hasMore: false,
12491
12654
  nextOffset: 0,
12492
12655
  shownCount: fixes.length,
12493
- currentTool: "fetch_available_fixes",
12656
+ currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
12494
12657
  offset: 0
12495
12658
  })}
12496
12659
  `;
@@ -12579,23 +12742,135 @@ var getLocalFiles = async ({
12579
12742
  return filesWithStats.filter((file) => file.lastEdited > 0);
12580
12743
  };
12581
12744
 
12582
- // src/mcp/services/ScanFiles.ts
12745
+ // src/mcp/services/FileOperations.ts
12583
12746
  import fs11 from "fs";
12584
12747
  import path12 from "path";
12585
12748
  import AdmZip2 from "adm-zip";
12749
+ var FileOperations = class {
12750
+ /**
12751
+ * Creates a ZIP archive containing the specified source files
12752
+ * @param fileList Array of relative file paths to include
12753
+ * @param repositoryPath Base path for resolving relative file paths
12754
+ * @param maxFileSize Maximum size allowed for individual files
12755
+ * @returns ZIP archive as a Buffer with metadata
12756
+ */
12757
+ async createSourceCodeArchive(fileList, repositoryPath, maxFileSize) {
12758
+ logDebug("FilePacking: packing files");
12759
+ const zip = new AdmZip2();
12760
+ let packedFilesCount = 0;
12761
+ const resolvedRepoPath = path12.resolve(repositoryPath);
12762
+ for (const filepath of fileList) {
12763
+ const absoluteFilepath = path12.join(repositoryPath, filepath);
12764
+ const resolvedFilePath = path12.resolve(absoluteFilepath);
12765
+ if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
12766
+ logDebug(
12767
+ `Skipping ${filepath} due to potential path traversal security risk`
12768
+ );
12769
+ continue;
12770
+ }
12771
+ if (!FileUtils.shouldPackFile(absoluteFilepath, maxFileSize)) {
12772
+ logDebug(
12773
+ `Excluding ${filepath} - file is too large, binary, or matches exclusion rules`
12774
+ );
12775
+ continue;
12776
+ }
12777
+ const fileContent = await this.readSourceFile(absoluteFilepath, filepath);
12778
+ if (fileContent) {
12779
+ zip.addFile(filepath, fileContent);
12780
+ packedFilesCount++;
12781
+ }
12782
+ }
12783
+ const archiveBuffer = zip.toBuffer();
12784
+ const result = {
12785
+ archive: archiveBuffer,
12786
+ packedFilesCount,
12787
+ totalSize: archiveBuffer.length
12788
+ };
12789
+ logInfo("Files packed successfully");
12790
+ return result;
12791
+ }
12792
+ /**
12793
+ * Validates that file paths are within the repository and safe to access
12794
+ * @param fileList Array of relative file paths to validate
12795
+ * @param repositoryPath Base path for validation
12796
+ * @returns Array of validated file paths
12797
+ */
12798
+ async validateFilePaths(fileList, repositoryPath) {
12799
+ const resolvedRepoPath = path12.resolve(repositoryPath);
12800
+ const validatedPaths = [];
12801
+ for (const filepath of fileList) {
12802
+ const absoluteFilepath = path12.join(repositoryPath, filepath);
12803
+ const resolvedFilePath = path12.resolve(absoluteFilepath);
12804
+ if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
12805
+ logDebug(`Rejecting ${filepath} - path traversal attempt detected`);
12806
+ continue;
12807
+ }
12808
+ try {
12809
+ await fs11.promises.access(absoluteFilepath, fs11.constants.R_OK);
12810
+ validatedPaths.push(filepath);
12811
+ } catch (error) {
12812
+ logDebug(`Skipping ${filepath} - file is not accessible: ${error}`);
12813
+ }
12814
+ }
12815
+ return validatedPaths;
12816
+ }
12817
+ /**
12818
+ * Reads source files and returns their data
12819
+ * @param filePaths Array of absolute file paths to read
12820
+ * @returns Array of file data objects
12821
+ */
12822
+ async readSourceFiles(filePaths) {
12823
+ const fileDataArray = [];
12824
+ for (const absolutePath of filePaths) {
12825
+ try {
12826
+ const content = await fs11.promises.readFile(absolutePath);
12827
+ const relativePath = path12.basename(absolutePath);
12828
+ fileDataArray.push({
12829
+ relativePath,
12830
+ absolutePath,
12831
+ content
12832
+ });
12833
+ } catch (error) {
12834
+ logError(`Failed to read file ${absolutePath}: ${error}`);
12835
+ }
12836
+ }
12837
+ return fileDataArray;
12838
+ }
12839
+ /**
12840
+ * Safely reads a single source file
12841
+ * @param absoluteFilepath Absolute path to the file
12842
+ * @param relativeFilepath Relative path for logging purposes
12843
+ * @returns File content as Buffer or null if failed
12844
+ */
12845
+ async readSourceFile(absoluteFilepath, relativeFilepath) {
12846
+ try {
12847
+ return await fs11.promises.readFile(absoluteFilepath);
12848
+ } catch (fsError) {
12849
+ logError(`Failed to read ${relativeFilepath} from filesystem: ${fsError}`);
12850
+ return null;
12851
+ }
12852
+ }
12853
+ };
12854
+
12855
+ // src/mcp/services/ScanFiles.ts
12586
12856
  var scanFiles = async (fileList, repositoryPath, gqlClient) => {
12587
- const repoUploadInfo = await initializeReport(gqlClient);
12857
+ const repoUploadInfo = await initializeSecurityReport(gqlClient);
12588
12858
  const fixReportId = repoUploadInfo.fixReportId;
12589
- const zipBuffer = await packFiles(fileList, repositoryPath);
12590
- await uploadFiles(zipBuffer, repoUploadInfo);
12859
+ const fileOperations = new FileOperations();
12860
+ const packingResult = await fileOperations.createSourceCodeArchive(
12861
+ fileList,
12862
+ repositoryPath,
12863
+ MCP_MAX_FILE_SIZE
12864
+ );
12865
+ await uploadSourceCodeArchive(packingResult.archive, repoUploadInfo);
12591
12866
  const projectId = await getProjectId(gqlClient);
12592
- await runScan({ fixReportId, projectId, gqlClient });
12867
+ await executeSecurityScan({ fixReportId, projectId, gqlClient });
12593
12868
  return {
12594
12869
  fixReportId,
12595
12870
  projectId
12596
12871
  };
12597
12872
  };
12598
- var initializeReport = async (gqlClient) => {
12873
+ var initializeSecurityReport = async (gqlClient) => {
12599
12874
  if (!gqlClient) {
12600
12875
  throw new GqlClientError();
12601
12876
  }
@@ -12603,74 +12878,33 @@ var initializeReport = async (gqlClient) => {
12603
12878
  const {
12604
12879
  uploadS3BucketInfo: { repoUploadInfo }
12605
12880
  } = await gqlClient.uploadS3BucketInfo();
12606
- logInfo("Upload info retrieved", { uploadKey: repoUploadInfo?.uploadKey });
12881
+ logDebug("Upload info retrieved");
12607
12882
  return repoUploadInfo;
12608
12883
  } catch (error) {
12609
12884
  const message = error.message;
12610
- throw new ReportInitializationError(`Error initializing report: ${message}`);
12611
- }
12612
- };
12613
- var packFiles = async (fileList, repositoryPath) => {
12614
- try {
12615
- logInfo(`FilePacking: packing files from ${repositoryPath}`);
12616
- const zip = new AdmZip2();
12617
- let packedFilesCount = 0;
12618
- const resolvedRepoPath = path12.resolve(repositoryPath);
12619
- logInfo("FilePacking: compressing files");
12620
- for (const filepath of fileList) {
12621
- const absoluteFilepath = path12.join(repositoryPath, filepath);
12622
- const resolvedFilePath = path12.resolve(absoluteFilepath);
12623
- if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
12624
- logInfo(
12625
- `FilePacking: skipping ${filepath} due to potential path traversal`
12626
- );
12627
- continue;
12628
- }
12629
- if (!FileUtils.shouldPackFile(absoluteFilepath, MCP_MAX_FILE_SIZE)) {
12630
- logInfo(
12631
- `FilePacking: ignoring ${filepath} because it is excluded or invalid`
12632
- );
12633
- continue;
12634
- }
12635
- let data;
12636
- try {
12637
- data = fs11.readFileSync(absoluteFilepath);
12638
- } catch (fsError) {
12639
- logInfo(
12640
- `FilePacking: failed to read ${filepath} from filesystem: ${fsError}`
12641
- );
12642
- continue;
12643
- }
12644
- zip.addFile(filepath, data);
12645
- packedFilesCount++;
12646
- }
12647
- const zipBuffer = zip.toBuffer();
12648
- logInfo(
12649
- `FilePacking: read ${packedFilesCount} source files. total size: ${zipBuffer.length} bytes`
12885
+ throw new ReportInitializationError(
12886
+ `Error initializing security report: ${message}`
12650
12887
  );
12651
- logInfo("Files packed successfully", { fileCount: fileList.length });
12652
- return zipBuffer;
12653
- } catch (error) {
12654
- const message = error.message;
12655
- throw new FileProcessingError(`Error packing files: ${message}`);
12656
12888
  }
12657
12889
  };
12658
- var uploadFiles = async (zipBuffer, repoUploadInfo) => {
12890
+ var uploadSourceCodeArchive = async (archiveBuffer, repoUploadInfo) => {
12659
12891
  if (!repoUploadInfo) {
12660
- throw new FileUploadError("Upload info is required");
12892
+ throw new FileUploadError("Upload info is required for source code archive");
12661
12893
  }
12662
12894
  try {
12663
12895
  await uploadFile({
12664
- file: zipBuffer,
12896
+ file: archiveBuffer,
12665
12897
  url: repoUploadInfo.url,
12666
12898
  uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
12667
12899
  uploadKey: repoUploadInfo.uploadKey
12668
12900
  });
12669
12901
  logInfo("File uploaded successfully");
12670
12902
  } catch (error) {
12671
- logError("File upload failed", { error: error.message });
12903
+ logError("Source code archive upload failed", {
12904
+ error: error.message
12905
+ });
12672
12906
  throw new FileUploadError(
12673
- `Failed to upload the file: ${error.message}`
12907
+ `Failed to upload source code archive: ${error.message}`
12674
12908
  );
12675
12909
  }
12676
12910
  };
@@ -12679,10 +12913,10 @@ var getProjectId = async (gqlClient) => {
12679
12913
  throw new GqlClientError();
12680
12914
  }
12681
12915
  const projectId = await gqlClient.getProjectId();
12682
- logInfo("Project ID retrieved", { projectId });
12916
+ logDebug("Project ID retrieved");
12683
12917
  return projectId;
12684
12918
  };
12685
- var runScan = async ({
12919
+ var executeSecurityScan = async ({
12686
12920
  fixReportId,
12687
12921
  projectId,
12688
12922
  gqlClient
@@ -12690,7 +12924,7 @@ var runScan = async ({
12690
12924
  if (!gqlClient) {
12691
12925
  throw new GqlClientError();
12692
12926
  }
12693
- logInfo("Starting scan", { fixReportId, projectId });
12927
+ logInfo("Starting scan");
12694
12928
  const submitVulnerabilityReportVariables = {
12695
12929
  fixReportId,
12696
12930
  projectId,
@@ -12703,25 +12937,28 @@ var runScan = async ({
12703
12937
  submitVulnerabilityReportVariables
12704
12938
  );
12705
12939
  if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
12706
- logError("Vulnerability report submission failed", {
12707
- response: submitRes
12940
+ throw new ScanError(
12941
+ `Security scan submission failed: ${submitRes.submitVulnerabilityReport.__typename}`
12942
+ );
12943
+ }
12944
+ const analysisId = submitRes.submitVulnerabilityReport.fixReportId;
12945
+ logInfo("Vulnerability report submitted successfully");
12946
+ try {
12947
+ await gqlClient.subscribeToGetAnalysis({
12948
+ subscribeToAnalysisParams: { analysisId },
12949
+ callback: async (completedAnalysisId) => {
12950
+ logInfo("Security analysis completed successfully", {
12951
+ analysisId: completedAnalysisId
12952
+ });
12953
+ },
12954
+ callbackStates: ["Finished" /* Finished */],
12955
+ timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
12708
12956
  });
12709
- throw new ScanError("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
12957
+ } catch (error) {
12958
+ logError("Security analysis failed or timed out", { error, analysisId });
12959
+ throw new ScanError(`Security analysis failed: ${error.message}`);
12710
12960
  }
12711
- logInfo("Vulnerability report submitted successfully", {
12712
- analysisId: submitRes.submitVulnerabilityReport.fixReportId
12713
- });
12714
- logInfo("Starting analysis subscription");
12715
- await gqlClient.subscribeToGetAnalysis({
12716
- subscribeToAnalysisParams: {
12717
- analysisId: submitRes.submitVulnerabilityReport.fixReportId
12718
- },
12719
- callback: () => {
12720
- },
12721
- callbackStates: ["Finished" /* Finished */],
12722
- timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
12723
- });
12724
- logInfo("Analysis subscription completed");
12961
+ logDebug("Security scan completed successfully", { fixReportId, projectId });
12725
12962
  };
12726
12963
 
12727
12964
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
@@ -12742,6 +12979,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12742
12979
  __publicField(this, "reportedFixes", []);
12743
12980
  __publicField(this, "intervalId", null);
12744
12981
  __publicField(this, "isInitialScanComplete", false);
12982
+ __publicField(this, "gqlClient", null);
12745
12983
  }
12746
12984
  static getInstance() {
12747
12985
  if (!_CheckForNewAvailableFixesService.instance) {
@@ -12763,25 +13001,28 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12763
13001
  }
12764
13002
  }
12765
13003
  /**
12766
- * Stub implementation in a future version this will query the backend for
12767
- * the latest fixes count and compare it with the cached value. For now it
12768
- * simply returns a placeholder string so that the tool can be wired into the
12769
- * system and used in tests.
13004
+ * Scans repository for security vulnerabilities and identifies new fixes
13005
+ * since the last scan.
12770
13006
  */
12771
- async scan({ path: path13 }) {
12772
- logInfo("Scanning for new fixes", { path: path13 });
12773
- const gqlClient = await getMcpGQLClient();
12774
- const isConnected = await gqlClient.verifyConnection();
13007
+ async scanForSecurityVulnerabilities({
13008
+ path: path13
13009
+ }) {
13010
+ logDebug("Scanning for new security vulnerabilities", { path: path13 });
13011
+ if (!this.gqlClient) {
13012
+ logInfo("No GQL client found, skipping scan");
13013
+ return;
13014
+ }
13015
+ const isConnected = await this.gqlClient.verifyApiConnection();
12775
13016
  if (!isConnected) {
12776
13017
  logError("Failed to connect to the API, scan aborted");
12777
13018
  return;
12778
13019
  }
12779
- logInfo("Connected to the API, assebling list of files to scan", { path: path13 });
13020
+ logDebug("Connected to the API, assembling list of files to scan", { path: path13 });
12780
13021
  const files = await getLocalFiles({
12781
13022
  path: path13,
12782
13023
  maxFileSize: MCP_MAX_FILE_SIZE
12783
13024
  });
12784
- logInfo("Active files", { files });
13025
+ logDebug("Active files", { files });
12785
13026
  const filesToScan = files.filter((file) => {
12786
13027
  const lastScannedEditTime = this.filesLastScanned[file.fullPath];
12787
13028
  if (!lastScannedEditTime) {
@@ -12790,34 +13031,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12790
13031
  return file.lastEdited > lastScannedEditTime;
12791
13032
  });
12792
13033
  if (filesToScan.length === 0) {
12793
- logInfo("No files to scan", { path: path13 });
13034
+ logInfo("No files require scanning");
12794
13035
  return;
12795
13036
  }
12796
- logInfo("Files to scan", { filesToScan });
13037
+ logDebug("Files requiring security scan", { filesToScan });
12797
13038
  const { fixReportId, projectId } = await scanFiles(
12798
13039
  filesToScan.map((file) => file.relativePath),
12799
13040
  path13,
12800
- gqlClient
13041
+ this.gqlClient
13042
+ );
13043
+ logInfo(
13044
+ `Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
12801
13045
  );
12802
- logInfo("Scan completed", { fixReportId, projectId });
12803
- const fixes = await gqlClient.getReportFixesPaginated({
13046
+ const fixes = await this.gqlClient.getReportFixesPaginated({
12804
13047
  reportId: fixReportId,
12805
13048
  offset: 0,
12806
13049
  limit: 1e3
12807
13050
  });
12808
- const newFixes = fixes?.fixes?.filter((fix) => !this.isAlreadyReported(fix));
12809
- logInfo("Fixes retrieved", {
12810
- count: fixes?.fixes?.length || 0,
12811
- newFixes: newFixes?.length || 0
13051
+ const newFixes = fixes?.fixes?.filter(
13052
+ (fix) => !this.isFixAlreadyReported(fix)
13053
+ );
13054
+ logInfo(
13055
+ `Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
13056
+ );
13057
+ this.updateFreshFixesCache(newFixes || [], filesToScan);
13058
+ this.updateFilesScanTimestamps(filesToScan);
13059
+ this.isInitialScanComplete = true;
13060
+ }
13061
+ updateFreshFixesCache(newFixes, filesToScan) {
13062
+ this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes).sort((a, b) => {
13063
+ return (b.severityValue ?? 0) - (a.severityValue ?? 0);
12812
13064
  });
12813
- this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes || []);
12814
- logInfo("Fresh fixes", { freshFixes: this.freshFixes });
13065
+ logInfo(`Fresh fixes cache updated, total: ${this.freshFixes.length}`);
13066
+ }
13067
+ updateFilesScanTimestamps(filesToScan) {
12815
13068
  filesToScan.forEach((file) => {
12816
13069
  this.filesLastScanned[file.fullPath] = file.lastEdited;
12817
13070
  });
12818
- this.isInitialScanComplete = true;
12819
13071
  }
12820
- isAlreadyReported(fix) {
13072
+ isFixAlreadyReported(fix) {
12821
13073
  return this.reportedFixes.some(
12822
13074
  (reportedFix) => reportedFix.sharedState?.id === fix.sharedState?.id
12823
13075
  );
@@ -12828,10 +13080,10 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12828
13080
  if (!fixFile) {
12829
13081
  return false;
12830
13082
  }
12831
- logInfo("isOldFix", {
13083
+ logDebug("Checking if fix is from old scan", {
12832
13084
  fixFile,
12833
13085
  filesToScan,
12834
- isOldFix: filesToScan.some((file) => file.relativePath === fixFile)
13086
+ isFromOldScan: filesToScan.some((file) => file.relativePath === fixFile)
12835
13087
  });
12836
13088
  return filesToScan.some((file) => file.relativePath === fixFile);
12837
13089
  }
@@ -12840,31 +13092,51 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12840
13092
  this.path = path13;
12841
13093
  this.reset();
12842
13094
  }
12843
- if (!this.intervalId) {
12844
- logInfo("Starting periodic scan for new fixes", { path: path13 });
12845
- this.intervalId = setInterval(() => {
12846
- logDebug("Triggering periodic scan", { path: path13 });
12847
- this.scan({ path: path13 }).catch((error) => {
12848
- logError("Error during periodic scan", { error });
12849
- });
12850
- }, MCP_PERIODIC_CHECK_INTERVAL);
12851
- logDebug("Triggering initial scan", { path: path13 });
12852
- this.scan({ path: path13 }).catch((error) => {
12853
- logError("Error during initial scan", { error });
12854
- });
12855
- }
13095
+ this.gqlClient = await createAuthenticatedMcpGQLClient();
13096
+ this.triggerScan({ path: path13, gqlClient: this.gqlClient });
12856
13097
  if (this.freshFixes.length > 0) {
12857
- const freshFixes = this.freshFixes.splice(0, 3);
12858
- if (freshFixes.length > 0) {
12859
- this.reportedFixes.concat(freshFixes);
12860
- return freshFixesPrompt({ fixes: freshFixes });
12861
- }
13098
+ return this.generateFreshFixesResponse();
12862
13099
  }
12863
13100
  if (!this.isInitialScanComplete) {
12864
13101
  return initialScanInProgressPrompt;
12865
13102
  }
12866
13103
  return noFreshFixesPrompt;
12867
13104
  }
13105
+ triggerScan({
13106
+ path: path13,
13107
+ gqlClient
13108
+ }) {
13109
+ this.gqlClient = gqlClient;
13110
+ if (!this.intervalId) {
13111
+ this.startPeriodicScanning(path13);
13112
+ this.executeInitialScan(path13);
13113
+ }
13114
+ }
13115
+ startPeriodicScanning(path13) {
13116
+ logDebug("Starting periodic scan for new security vulnerabilities", {
13117
+ path: path13
13118
+ });
13119
+ this.intervalId = setInterval(() => {
13120
+ logDebug("Triggering periodic security scan", { path: path13 });
13121
+ this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
13122
+ logError("Error during periodic security scan", { error });
13123
+ });
13124
+ }, MCP_PERIODIC_CHECK_INTERVAL);
13125
+ }
13126
+ executeInitialScan(path13) {
13127
+ logDebug("Triggering initial security scan", { path: path13 });
13128
+ this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
13129
+ logError("Error during initial security scan", { error });
13130
+ });
13131
+ }
13132
+ generateFreshFixesResponse() {
13133
+ const freshFixes = this.freshFixes.splice(0, 3);
13134
+ if (freshFixes.length > 0) {
13135
+ this.reportedFixes.push(...freshFixes);
13136
+ return freshFixesPrompt({ fixes: freshFixes });
13137
+ }
13138
+ return noFreshFixesPrompt;
13139
+ }
12868
13140
  };
12869
13141
  __publicField(_CheckForNewAvailableFixesService, "instance");
12870
13142
  var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
@@ -12873,7 +13145,7 @@ var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
12873
13145
  var CheckForNewAvailableFixesTool = class extends BaseTool {
12874
13146
  constructor() {
12875
13147
  super();
12876
- __publicField(this, "name", "check_for_new_available_fixes");
13148
+ __publicField(this, "name", MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES);
12877
13149
  __publicField(this, "displayName", "Check for New Available Fixes");
12878
13150
  // A detailed description to guide the LLM on when and how to invoke this tool.
12879
13151
  __publicField(this, "description", `Continuesly monitors your code and scans for new security vulnerabilities.
@@ -12913,6 +13185,9 @@ Example payload:
12913
13185
  __publicField(this, "newFixesService");
12914
13186
  this.newFixesService = new CheckForNewAvailableFixesService();
12915
13187
  }
13188
+ triggerScan(args) {
13189
+ this.newFixesService.triggerScan(args);
13190
+ }
12916
13191
  async executeInternal(args) {
12917
13192
  const pathValidationResult = await validatePath(args.path);
12918
13193
  if (!pathValidationResult.isValid) {
@@ -12927,14 +13202,7 @@ Example payload:
12927
13202
  logInfo("CheckForNewAvailableFixesTool execution completed", {
12928
13203
  resultText
12929
13204
  });
12930
- return {
12931
- content: [
12932
- {
12933
- type: "text",
12934
- text: resultText
12935
- }
12936
- ]
12937
- };
13205
+ return this.createSuccessResponse(resultText);
12938
13206
  }
12939
13207
  };
12940
13208
 
@@ -12958,7 +13226,7 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
12958
13226
  }
12959
13227
  async initializeGqlClient() {
12960
13228
  if (!this.gqlClient) {
12961
- this.gqlClient = await getMcpGQLClient();
13229
+ this.gqlClient = await createAuthenticatedMcpGQLClient();
12962
13230
  }
12963
13231
  return this.gqlClient;
12964
13232
  }
@@ -12982,21 +13250,18 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
12982
13250
  logDebug("received latest report result", { fixReport, expiredReport });
12983
13251
  if (!fixReport) {
12984
13252
  if (expiredReport) {
13253
+ logInfo("Expired report found");
12985
13254
  const lastReportDate = expiredReport.expirationOn ? new Date(expiredReport.expirationOn).toLocaleString() : "Unknown date";
12986
- logInfo("Expired report found", {
13255
+ logDebug("Expired report ", {
12987
13256
  repoUrl,
12988
13257
  expirationOn: expiredReport.expirationOn
12989
13258
  });
12990
13259
  return expiredReportPrompt({ lastReportDate });
12991
13260
  }
12992
- logInfo("No report (active or expired) found for repository", {
12993
- repoUrl
12994
- });
13261
+ logInfo(`No report (active or expired) found for repository ${repoUrl}`);
12995
13262
  return noReportFoundPrompt;
12996
13263
  }
12997
- logInfo("Successfully retrieved available fixes", {
12998
- reportFound: true
12999
- });
13264
+ logInfo(`Successfully retrieved available fixes for ${repoUrl}`);
13000
13265
  const prompt = fixesFoundPrompt({
13001
13266
  fixReport,
13002
13267
  offset: effectiveOffset
@@ -13019,7 +13284,7 @@ var FetchAvailableFixesService = _FetchAvailableFixesService;
13019
13284
  var FetchAvailableFixesTool = class extends BaseTool {
13020
13285
  constructor() {
13021
13286
  super();
13022
- __publicField(this, "name", "fetch_available_fixes");
13287
+ __publicField(this, "name", MCP_TOOL_FETCH_AVAILABLE_FIXES);
13023
13288
  __publicField(this, "displayName", "Fetch Available Fixes");
13024
13289
  __publicField(this, "description", `Check the MOBB backend for pre-generated fixes (patch sets) that correspond to vulnerabilities detected in the supplied Git repository.
13025
13290
 
@@ -13039,7 +13304,7 @@ The tool will:
13039
13304
  2. Verify that the directory is a valid Git repository with an "origin" remote.
13040
13305
  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.
13041
13306
 
13042
- 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.`);
13307
+ Call this tool instead of ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES} when you only need a fixes summary and do NOT want to perform scanning or code modifications.`);
13043
13308
  __publicField(this, "inputSchema", {
13044
13309
  type: "object",
13045
13310
  properties: {
@@ -13095,17 +13360,10 @@ Call this tool instead of scan_and_fix_vulnerabilities when you only need a fixe
13095
13360
  limit: args.limit,
13096
13361
  offset: args.offset
13097
13362
  });
13098
- logInfo("FetchAvailableFixesTool execution completed successfully", {
13363
+ logDebug("FetchAvailableFixesTool execution completed successfully", {
13099
13364
  fixResult
13100
13365
  });
13101
- return {
13102
- content: [
13103
- {
13104
- type: "text",
13105
- text: fixResult
13106
- }
13107
- ]
13108
- };
13366
+ return this.createSuccessResponse(fixResult);
13109
13367
  }
13110
13368
  };
13111
13369
 
@@ -13157,9 +13415,10 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13157
13415
  limit,
13158
13416
  isRescan = false
13159
13417
  }) {
13418
+ logInfo("Processing vulnerabilities");
13160
13419
  try {
13161
13420
  this.gqlClient = await this.initializeGqlClient();
13162
- logInfo("storedFixReportId", {
13421
+ logDebug("storedFixReportId", {
13163
13422
  storedFixReportId: this.storedFixReportId,
13164
13423
  currentOffset: this.currentOffset,
13165
13424
  fixReportIdTimestamp: this.fixReportIdTimestamp,
@@ -13167,6 +13426,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13167
13426
  });
13168
13427
  let fixReportId = this.storedFixReportId;
13169
13428
  if (!fixReportId || isRescan || this.isFixReportIdExpired()) {
13429
+ logInfo("Scanning files");
13170
13430
  this.reset();
13171
13431
  this.validateFiles(fileList);
13172
13432
  const scanResult = await scanFiles(
@@ -13175,6 +13435,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13175
13435
  this.gqlClient
13176
13436
  );
13177
13437
  fixReportId = scanResult.fixReportId;
13438
+ } else {
13439
+ logInfo("Using stored fixReportId");
13178
13440
  }
13179
13441
  const effectiveOffset = offset ?? (this.currentOffset || 0);
13180
13442
  logDebug("effectiveOffset", { effectiveOffset });
@@ -13183,6 +13445,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13183
13445
  effectiveOffset,
13184
13446
  limit
13185
13447
  );
13448
+ logInfo(`Found ${fixes.totalCount} fixes`);
13186
13449
  if (fixes.totalCount > 0) {
13187
13450
  this.storedFixReportId = fixReportId;
13188
13451
  this.fixReportIdTimestamp = Date.now();
@@ -13209,13 +13472,14 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13209
13472
  }
13210
13473
  }
13211
13474
  async initializeGqlClient() {
13212
- const gqlClient = await getMcpGQLClient();
13213
- const isConnected = await gqlClient.verifyConnection();
13475
+ const gqlClient = await createAuthenticatedMcpGQLClient();
13476
+ const isConnected = await gqlClient.verifyApiConnection();
13214
13477
  if (!isConnected) {
13215
13478
  throw new ApiConnectionError(
13216
13479
  "Failed to connect to the API. Please check your MOBB_API_KEY"
13217
13480
  );
13218
13481
  }
13482
+ this.gqlClient = gqlClient;
13219
13483
  return gqlClient;
13220
13484
  }
13221
13485
  async getReportFixes(fixReportId, offset, limit) {
@@ -13228,7 +13492,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13228
13492
  offset,
13229
13493
  limit
13230
13494
  });
13231
- logInfo("Fixes retrieved", { fixCount: fixes?.fixes?.length });
13495
+ logDebug(`${fixes?.fixes?.length} fixes retrieved`);
13232
13496
  return {
13233
13497
  fixes: fixes?.fixes || [],
13234
13498
  totalCount: fixes?.totalCount || 0
@@ -13242,7 +13506,7 @@ var ScanAndFixVulnerabilitiesService = _ScanAndFixVulnerabilitiesService;
13242
13506
  var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
13243
13507
  constructor() {
13244
13508
  super();
13245
- __publicField(this, "name", "scan_and_fix_vulnerabilities");
13509
+ __publicField(this, "name", MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES);
13246
13510
  __publicField(this, "displayName", "Scan and Fix Vulnerabilities");
13247
13511
  // A detailed description to guide the LLM on when and how to invoke this tool.
13248
13512
  __publicField(this, "description", `Scans a given local repository for security vulnerabilities and returns auto-generated code fixes.
@@ -13322,7 +13586,9 @@ Example payload:
13322
13586
  this.vulnerabilityFixService.reset();
13323
13587
  }
13324
13588
  async executeInternal(args) {
13325
- logInfo("Executing tool: scan_and_fix_vulnerabilities", { path: args.path });
13589
+ logDebug(`Executing tool: ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}`, {
13590
+ path: args.path
13591
+ });
13326
13592
  if (!args.path) {
13327
13593
  throw new Error("Invalid arguments: Missing required parameter 'path'");
13328
13594
  }
@@ -13339,7 +13605,7 @@ Example payload:
13339
13605
  // 5MB
13340
13606
  maxFiles: args.maxFiles
13341
13607
  });
13342
- logInfo("Files", { files });
13608
+ logDebug("Files", { files });
13343
13609
  if (files.length === 0) {
13344
13610
  return {
13345
13611
  content: [
@@ -13358,34 +13624,20 @@ Example payload:
13358
13624
  limit: args.limit,
13359
13625
  isRescan: args.rescan || !!args.maxFiles
13360
13626
  });
13361
- const result = {
13362
- content: [
13363
- {
13364
- type: "text",
13365
- text: fixResult
13366
- }
13367
- ]
13368
- };
13369
- logInfo("Tool execution completed successfully", {
13627
+ const successResponse = this.createSuccessResponse(fixResult);
13628
+ logDebug("Tool execution completed successfully", {
13370
13629
  resultLength: fixResult.length,
13371
13630
  fileCount: files.length,
13372
- result
13631
+ result: successResponse
13373
13632
  });
13374
- return result;
13633
+ return successResponse;
13375
13634
  } catch (error) {
13376
- const errorResult = {
13377
- content: [
13378
- {
13379
- type: "text",
13380
- text: error.message
13381
- }
13382
- ]
13383
- };
13384
- logInfo("Tool execution failed", {
13635
+ const errorResponse = this.createSuccessResponse(error.message);
13636
+ logError("Tool execution failed", {
13385
13637
  error: error.message,
13386
- result: errorResult
13638
+ result: errorResponse
13387
13639
  });
13388
- return errorResult;
13640
+ return errorResponse;
13389
13641
  }
13390
13642
  }
13391
13643
  };