mobbdev 1.0.113 → 1.0.116

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 +683 -425
  2. package/package.json +25 -22
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";
@@ -444,6 +445,7 @@ var IssueType_Enum = /* @__PURE__ */ ((IssueType_Enum2) => {
444
445
  IssueType_Enum2["SqlInjection"] = "SQL_Injection";
445
446
  IssueType_Enum2["Ssrf"] = "SSRF";
446
447
  IssueType_Enum2["StringFormatMisuse"] = "STRING_FORMAT_MISUSE";
448
+ IssueType_Enum2["StringTerminationError"] = "STRING_TERMINATION_ERROR";
447
449
  IssueType_Enum2["SystemExitShouldReraise"] = "SYSTEM_EXIT_SHOULD_RERAISE";
448
450
  IssueType_Enum2["SystemInformationLeak"] = "SYSTEM_INFORMATION_LEAK";
449
451
  IssueType_Enum2["SystemInformationLeakExternal"] = "SYSTEM_INFORMATION_LEAK_EXTERNAL";
@@ -533,6 +535,8 @@ var FixDetailsFragmentDoc = `
533
535
  confidence
534
536
  safeIssueType
535
537
  severityText
538
+ gitBlameLogin
539
+ severityValue
536
540
  vulnerabilityReportIssues {
537
541
  parsedIssueType
538
542
  parsedSeverity
@@ -596,7 +600,15 @@ var FixReportSummaryFieldsFragmentDoc = `
596
600
  }
597
601
  }
598
602
  fixes(
599
- where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
603
+ where: {_and: [{vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, {_or: [{gitBlameLogin: {_is_null: true}}, {_not: {gitBlameLogin: {_ilike: $currentUserEmail}}}]}, $filters]}
604
+ order_by: {severityValue: desc}
605
+ limit: $limit
606
+ offset: $offset
607
+ ) {
608
+ ...FixDetails
609
+ }
610
+ userFixes: fixes(
611
+ where: {_and: [{gitBlameLogin: {_ilike: $currentUserEmail}}, {vulnerabilityReportIssues: {category: {_eq: "Fixable"}}}, $filters]}
600
612
  order_by: {severityValue: desc}
601
613
  limit: $limit
602
614
  offset: $offset
@@ -662,18 +674,18 @@ var MeDocument = `
662
674
  }
663
675
  }
664
676
  `;
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 {
677
+ var GetLastOrgAndNamedProjectDocument = `
678
+ query getLastOrgAndNamedProject($email: String!, $projectName: String!) {
679
+ user(where: {email: {_eq: $email}}) {
680
+ id
681
+ userOrganizationsAndUserOrganizationRoles(order_by: {createdOn: desc}) {
673
682
  id
674
- projects(order_by: {updatedAt: desc}) {
683
+ organization {
675
684
  id
676
- name
685
+ projects(where: {name: {_eq: $projectName}}) {
686
+ name
687
+ id
688
+ }
677
689
  }
678
690
  }
679
691
  }
@@ -1064,32 +1076,32 @@ var AutoPrAnalysisDocument = `
1064
1076
  }
1065
1077
  }
1066
1078
  `;
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
- ) {
1079
+ var GetReportFixesDocument = `
1080
+ query GetReportFixes($reportId: uuid!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
1081
+ fixReport(where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Finished}}]}) {
1074
1082
  ...FixReportSummaryFields
1075
1083
  }
1076
1084
  expiredReport: fixReport(
1077
- where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
1078
- order_by: {createdOn: desc}
1079
- limit: 1
1085
+ where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Expired}}]}
1080
1086
  ) {
1081
1087
  id
1082
1088
  expirationOn
1083
1089
  }
1084
1090
  }
1085
1091
  ${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}}]}) {
1092
+ var GetLatestReportByRepoUrlDocument = `
1093
+ query GetLatestReportByRepoUrl($repoUrl: String!, $filters: fix_bool_exp = {}, $limit: Int!, $offset: Int!, $currentUserEmail: String!) {
1094
+ fixReport(
1095
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Finished}}]}
1096
+ order_by: {createdOn: desc}
1097
+ limit: 1
1098
+ ) {
1089
1099
  ...FixReportSummaryFields
1090
1100
  }
1091
1101
  expiredReport: fixReport(
1092
- where: {_and: [{id: {_eq: $reportId}}, {state: {_eq: Expired}}]}
1102
+ where: {_and: [{repo: {originalUrl: {_eq: $repoUrl}}}, {state: {_eq: Expired}}]}
1103
+ order_by: {createdOn: desc}
1104
+ limit: 1
1093
1105
  ) {
1094
1106
  id
1095
1107
  expirationOn
@@ -1109,8 +1121,8 @@ function getSdk(client, withWrapper = defaultWrapper) {
1109
1121
  Me(variables, requestHeaders, signal) {
1110
1122
  return withWrapper((wrappedRequestHeaders) => client.request({ document: MeDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "Me", "query", variables);
1111
1123
  },
1112
- getOrgAndProjectId(variables, requestHeaders, signal) {
1113
- return withWrapper((wrappedRequestHeaders) => client.request({ document: GetOrgAndProjectIdDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getOrgAndProjectId", "query", variables);
1124
+ getLastOrgAndNamedProject(variables, requestHeaders, signal) {
1125
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLastOrgAndNamedProjectDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getLastOrgAndNamedProject", "query", variables);
1114
1126
  },
1115
1127
  GetEncryptedApiToken(variables, requestHeaders, signal) {
1116
1128
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetEncryptedApiTokenDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetEncryptedApiToken", "query", variables);
@@ -1169,12 +1181,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
1169
1181
  autoPrAnalysis(variables, requestHeaders, signal) {
1170
1182
  return withWrapper((wrappedRequestHeaders) => client.request({ document: AutoPrAnalysisDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "autoPrAnalysis", "mutation", variables);
1171
1183
  },
1172
- GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
1173
- return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
1174
- },
1175
1184
  GetReportFixes(variables, requestHeaders, signal) {
1176
1185
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetReportFixesDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetReportFixes", "query", variables);
1177
1186
  },
1187
+ GetLatestReportByRepoUrl(variables, requestHeaders, signal) {
1188
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetLatestReportByRepoUrlDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "GetLatestReportByRepoUrl", "query", variables);
1189
+ },
1178
1190
  updateDownloadedFixData(variables, requestHeaders, signal) {
1179
1191
  return withWrapper((wrappedRequestHeaders) => client.request({ document: UpdateDownloadedFixDataDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "updateDownloadedFixData", "mutation", variables);
1180
1192
  }
@@ -1444,7 +1456,15 @@ var fixDetailsData = {
1444
1456
  ["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: void 0,
1445
1457
  ["NO_NESTED_TRY" /* NoNestedTry */]: void 0,
1446
1458
  ["REDOS" /* Redos */]: void 0,
1447
- ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0
1459
+ ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: void 0,
1460
+ ["BUFFER_OVERFLOW" /* BufferOverflow */]: {
1461
+ issueDescription: "Buffer Overflow occurs when a program writes data beyond the allocated memory space, leading to unexpected behavior or security vulnerabilities.",
1462
+ 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."
1463
+ },
1464
+ ["STRING_TERMINATION_ERROR" /* StringTerminationError */]: {
1465
+ issueDescription: "String Termination Error occurs when a string is not properly terminated, leading to unexpected behavior or security vulnerabilities.",
1466
+ fixInstructions: "Implement proper input validation and bounds checking to prevent string termination errors. Use safe string manipulation functions and ensure that the buffer size is properly managed."
1467
+ }
1448
1468
  };
1449
1469
 
1450
1470
  // src/features/analysis/scm/shared/src/getIssueType.ts
@@ -1558,7 +1578,9 @@ var issueTypeMap = {
1558
1578
  ["NO_NESTED_TRY" /* NoNestedTry */]: "No Nested Try",
1559
1579
  ["UNNECESSARY_IMPORTS" /* UnnecessaryImports */]: "Unnecessary Imports",
1560
1580
  ["REDOS" /* Redos */]: "Regular Expression Denial of Service",
1561
- ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception"
1581
+ ["DO_NOT_THROW_GENERIC_EXCEPTION" /* DoNotThrowGenericException */]: "Do Not Throw Generic Exception",
1582
+ ["BUFFER_OVERFLOW" /* BufferOverflow */]: "Buffer Overflow",
1583
+ ["STRING_TERMINATION_ERROR" /* StringTerminationError */]: "String Termination Error"
1562
1584
  };
1563
1585
  var issueTypeZ = z.nativeEnum(IssueType_Enum);
1564
1586
  var getIssueTypeFriendlyString = (issueType) => {
@@ -5141,14 +5163,17 @@ import { simpleGit } from "simple-git";
5141
5163
  // src/mcp/core/configs.ts
5142
5164
  var MCP_DEFAULT_API_URL = "https://api.mobb.ai/v1/graphql";
5143
5165
  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;
5166
+ var MCP_LOGIN_MAX_WAIT = 2 * 60 * 1e3;
5167
+ var MCP_LOGIN_CHECK_DELAY = 2 * 1e3;
5146
5168
  var MCP_VUL_REPORT_DIGEST_TIMEOUT_MS = 5 * 60 * 1e3;
5147
5169
  var MCP_MAX_FILE_SIZE = MAX_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
5148
5170
  var MCP_PERIODIC_CHECK_INTERVAL = 15 * 60 * 1e3;
5149
5171
  var MCP_DEFAULT_MAX_FILES_TO_SCAN = 10;
5150
5172
  var MCP_REPORT_ID_EXPIRATION_MS = 2 * 60 * 60 * 1e3;
5151
5173
  var MCP_TOOLS_BROWSER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
5174
+ var MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES = "check_for_new_available_fixes";
5175
+ var MCP_TOOL_FETCH_AVAILABLE_FIXES = "fetch_available_fixes";
5176
+ var MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES = "scan_and_fix_vulnerabilities";
5152
5177
 
5153
5178
  // src/features/analysis/scm/FileUtils.ts
5154
5179
  import fs2 from "fs";
@@ -8653,12 +8678,6 @@ var GqlClientError = class extends Error {
8653
8678
  this.name = "GqlClientError";
8654
8679
  }
8655
8680
  };
8656
- var FileProcessingError = class extends Error {
8657
- constructor(message) {
8658
- super(message);
8659
- this.name = "FileProcessingError";
8660
- }
8661
- };
8662
8681
  var ReportInitializationError = class extends Error {
8663
8682
  constructor(message) {
8664
8683
  super(message);
@@ -9592,7 +9611,7 @@ var GQLClient = class {
9592
9611
  });
9593
9612
  return res.insert_cli_login_one?.id || "";
9594
9613
  }
9595
- async verifyConnection() {
9614
+ async verifyApiConnection() {
9596
9615
  try {
9597
9616
  await this.getUserInfo();
9598
9617
  } catch (e) {
@@ -9603,7 +9622,7 @@ var GQLClient = class {
9603
9622
  }
9604
9623
  return true;
9605
9624
  }
9606
- async verifyToken() {
9625
+ async validateUserToken() {
9607
9626
  await this.createCommunityUser();
9608
9627
  let info;
9609
9628
  try {
@@ -9614,31 +9633,46 @@ var GQLClient = class {
9614
9633
  }
9615
9634
  return info?.email || true;
9616
9635
  }
9617
- async getOrgAndProjectId(params = {}) {
9636
+ async getLastOrgAndNamedProject(params) {
9637
+ const me = await this.getUserInfo();
9638
+ const email = me?.email;
9639
+ if (!email) {
9640
+ throw new Error("User email not found");
9641
+ }
9618
9642
  const { projectName, userDefinedOrganizationId } = params;
9619
- const getOrgAndProjectIdResult = await this._clientSdk.getOrgAndProjectId({
9620
- filters: userDefinedOrganizationId ? { organizationId: { _eq: userDefinedOrganizationId } } : {},
9621
- limit: 1
9643
+ if (!projectName) {
9644
+ throw new Error("Project name is required");
9645
+ }
9646
+ const orgAndProjectRes = await this._clientSdk.getLastOrgAndNamedProject({
9647
+ email,
9648
+ projectName
9622
9649
  });
9623
- const [organizationToOrganizationRole] = getOrgAndProjectIdResult.organization_to_organization_role;
9624
- if (!organizationToOrganizationRole) {
9625
- throw new Error("Organization not found");
9650
+ if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
9651
+ throw new Error(
9652
+ `The user with email:${email} is not associated with any organization`
9653
+ );
9654
+ }
9655
+ const organization = orgAndProjectRes.user?.at(0)?.userOrganizationsAndUserOrganizationRoles.map((org) => org.organization).filter(
9656
+ (org) => userDefinedOrganizationId ? org.id === userDefinedOrganizationId : true
9657
+ )?.at(0);
9658
+ if (!organization) {
9659
+ throw new Error(
9660
+ `Organization with id:${userDefinedOrganizationId} not found`
9661
+ );
9626
9662
  }
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;
9663
+ let projectId = organization?.projects?.[0]?.id;
9630
9664
  if (!projectId) {
9631
9665
  const createdProject = await this._clientSdk.CreateProject({
9632
- organizationId: org.id,
9666
+ organizationId: organization.id,
9633
9667
  projectName: projectName || "My project"
9634
9668
  });
9635
9669
  projectId = createdProject.createProject.projectId;
9636
9670
  }
9637
- if (!project?.id) {
9671
+ if (!projectId) {
9638
9672
  throw new Error("Project not found");
9639
9673
  }
9640
9674
  return {
9641
- organizationId: org.id,
9675
+ organizationId: organization.id,
9642
9676
  projectId
9643
9677
  };
9644
9678
  }
@@ -10551,7 +10585,10 @@ async function _scan(params, { skipPrompts = false } = {}) {
10551
10585
  skipPrompts,
10552
10586
  apiKey
10553
10587
  });
10554
- const { projectId, organizationId } = await gqlClient.getOrgAndProjectId({
10588
+ if (!mobbProjectName) {
10589
+ throw new Error("mobbProjectName is required");
10590
+ }
10591
+ const { projectId, organizationId } = await gqlClient.getLastOrgAndNamedProject({
10555
10592
  projectName: mobbProjectName,
10556
10593
  userDefinedOrganizationId: userOrganizationId
10557
10594
  });
@@ -11128,7 +11165,7 @@ async function handleMobbLogin({
11128
11165
  skipPrompts
11129
11166
  }) {
11130
11167
  const { createSpinner: createSpinner5 } = Spinner({ ci: skipPrompts });
11131
- const isConnected = await inGqlClient.verifyConnection();
11168
+ const isConnected = await inGqlClient.verifyApiConnection();
11132
11169
  if (!isConnected) {
11133
11170
  createSpinner5().start().error({
11134
11171
  text: "\u{1F513} Connection to Mobb: failed to connect to the Mobb server"
@@ -11140,7 +11177,7 @@ async function handleMobbLogin({
11140
11177
  createSpinner5().start().success({
11141
11178
  text: `\u{1F513} Connection to Mobb: succeeded`
11142
11179
  });
11143
- const userVerify = await inGqlClient.verifyToken();
11180
+ const userVerify = await inGqlClient.validateUserToken();
11144
11181
  if (userVerify) {
11145
11182
  createSpinner5().start().success({
11146
11183
  text: `\u{1F513} Login to Mobb succeeded. ${typeof userVerify === "string" ? `Logged in as ${userVerify}` : ""}`
@@ -11194,7 +11231,7 @@ async function handleMobbLogin({
11194
11231
  throw new CliError();
11195
11232
  }
11196
11233
  const newGqlClient = new GQLClient({ apiKey: newApiToken, type: "apiKey" });
11197
- const loginSuccess = await newGqlClient.verifyToken();
11234
+ const loginSuccess = await newGqlClient.validateUserToken();
11198
11235
  if (loginSuccess) {
11199
11236
  debug18(`set api token ${newApiToken}`);
11200
11237
  config3.set("apiToken", newApiToken);
@@ -11353,7 +11390,7 @@ import {
11353
11390
  } from "@modelcontextprotocol/sdk/types.js";
11354
11391
 
11355
11392
  // src/mcp/Logger.ts
11356
- var logglerUrl = "http://localhost:4444/log";
11393
+ var loggerUrl = "http://localhost:4444/log";
11357
11394
  var isTestEnvironment = process.env["VITEST"] || process.env["TEST"];
11358
11395
  var CIRCUIT_BREAKER_TIME = 5e3;
11359
11396
  var URL_CHECK_TIMEOUT = 200;
@@ -11400,11 +11437,14 @@ var Logger = class {
11400
11437
  this.isProcessing = false;
11401
11438
  return;
11402
11439
  }
11403
- const isReachable = await this.isUrlReachable(logglerUrl);
11440
+ const isReachable = await this.isUrlReachable(loggerUrl);
11404
11441
  if (!isReachable) {
11405
11442
  this.triggerCircuitBreaker();
11406
11443
  return;
11407
11444
  }
11445
+ await this.sendLogEntry(logEntry);
11446
+ }
11447
+ async sendLogEntry(logEntry) {
11408
11448
  const logMessage = {
11409
11449
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11410
11450
  level: logEntry.level,
@@ -11416,21 +11456,23 @@ var Logger = class {
11416
11456
  const timeoutId = setTimeout(() => {
11417
11457
  controller.abort();
11418
11458
  }, 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) => {
11459
+ try {
11460
+ await fetch(loggerUrl, {
11461
+ method: "POST",
11462
+ headers: { "Content-Type": "application/json" },
11463
+ body: JSON.stringify(logMessage),
11464
+ redirect: "error",
11465
+ // do not follow redirects
11466
+ signal: controller.signal
11467
+ });
11427
11468
  this.queue.shift();
11428
11469
  setTimeout(() => this.processQueue(), 0);
11429
- }).catch(() => {
11470
+ } catch (error) {
11430
11471
  this.triggerCircuitBreaker();
11431
- }).finally(() => {
11472
+ logError("Failed to send log entry", error);
11473
+ } finally {
11432
11474
  clearTimeout(timeoutId);
11433
- });
11475
+ }
11434
11476
  }
11435
11477
  triggerCircuitBreaker() {
11436
11478
  this.isCircuitBroken = true;
@@ -11456,12 +11498,90 @@ var logDebug = (message, data) => logger.log(message, "debug", data);
11456
11498
  var log = logger.log.bind(logger);
11457
11499
 
11458
11500
  // src/mcp/services/McpGQLClient.ts
11459
- import crypto2 from "crypto";
11460
- import os2 from "os";
11461
11501
  import Configstore3 from "configstore";
11502
+ import crypto3 from "crypto";
11462
11503
  import { GraphQLClient as GraphQLClient2 } from "graphql-request";
11463
- import open4 from "open";
11464
11504
  import { v4 as uuidv42 } from "uuid";
11505
+
11506
+ // src/mcp/services/McpAuthService.ts
11507
+ import crypto2 from "crypto";
11508
+ import os2 from "os";
11509
+ import open4 from "open";
11510
+ var McpAuthService = class {
11511
+ constructor(client) {
11512
+ __publicField(this, "client");
11513
+ __publicField(this, "lastBrowserOpenTime", 0);
11514
+ this.client = client;
11515
+ }
11516
+ /**
11517
+ * Opens a browser window for authentication
11518
+ * @param url URL to open in browser
11519
+ * @param isBackgoundCall Whether this is called from tools context
11520
+ */
11521
+ async openBrowser(url, isBackgoundCall) {
11522
+ if (isBackgoundCall) {
11523
+ const now = Date.now();
11524
+ if (now - this.lastBrowserOpenTime < MCP_TOOLS_BROWSER_COOLDOWN_MS) {
11525
+ logDebug(`browser cooldown active, skipping open for ${url}`);
11526
+ return;
11527
+ }
11528
+ }
11529
+ logDebug(`opening browser url ${url}`);
11530
+ await open4(url);
11531
+ this.lastBrowserOpenTime = Date.now();
11532
+ }
11533
+ /**
11534
+ * Handles the complete authentication flow
11535
+ * @param isBackgoundCall Whether this is called from tools context
11536
+ * @returns Authenticated API token
11537
+ */
11538
+ async authenticate(isBackgoundCall = false) {
11539
+ const { publicKey, privateKey } = crypto2.generateKeyPairSync("rsa", {
11540
+ modulusLength: 2048
11541
+ });
11542
+ logDebug("creating cli login");
11543
+ const loginId = await this.client.createCliLogin({
11544
+ publicKey: publicKey.export({ format: "pem", type: "pkcs1" }).toString()
11545
+ });
11546
+ if (!loginId) {
11547
+ throw new CliLoginError("Error: createCliLogin failed");
11548
+ }
11549
+ logDebug(`cli login created ${loginId}`);
11550
+ const webLoginUrl2 = `${WEB_APP_URL}/cli-login`;
11551
+ const browserUrl = `${webLoginUrl2}/${loginId}?hostname=${os2.hostname()}`;
11552
+ await this.openBrowser(browserUrl, isBackgoundCall);
11553
+ logDebug(`waiting for login to complete`);
11554
+ let newApiToken = null;
11555
+ for (let i = 0; i < MCP_LOGIN_MAX_WAIT / MCP_LOGIN_CHECK_DELAY; i++) {
11556
+ const encryptedApiToken = await this.client.getEncryptedApiToken({
11557
+ loginId
11558
+ });
11559
+ if (encryptedApiToken) {
11560
+ logDebug("encrypted API token received");
11561
+ newApiToken = crypto2.privateDecrypt(privateKey, Buffer.from(encryptedApiToken, "base64")).toString("utf-8");
11562
+ logDebug("API token decrypted");
11563
+ break;
11564
+ }
11565
+ await sleep(MCP_LOGIN_CHECK_DELAY);
11566
+ }
11567
+ if (!newApiToken) {
11568
+ throw new FailedToGetApiTokenError(
11569
+ "Error: failed to get encrypted api token"
11570
+ );
11571
+ }
11572
+ const verifyClient = new McpGQLClient({
11573
+ apiKey: newApiToken,
11574
+ type: "apiKey"
11575
+ });
11576
+ const loginSuccess = await verifyClient.validateUserToken();
11577
+ if (!loginSuccess) {
11578
+ throw new AuthenticationError("Invalid API token");
11579
+ }
11580
+ return newApiToken;
11581
+ }
11582
+ };
11583
+
11584
+ // src/mcp/services/McpGQLClient.ts
11465
11585
  var mobbConfigStore = new Configstore3(packageJson.name, { apiToken: "" });
11466
11586
  var McpGQLClient = class {
11467
11587
  constructor(args) {
@@ -11498,17 +11618,17 @@ var McpGQLClient = class {
11498
11618
  }
11499
11619
  };
11500
11620
  }
11501
- async verifyConnection() {
11621
+ async verifyApiConnection() {
11502
11622
  try {
11503
- logDebug("GraphQL: Calling Me query for connection verification");
11623
+ logDebug("GraphQL: Calling Me query for API connection verification");
11504
11624
  const result = await this.clientSdk.Me();
11505
11625
  logDebug("GraphQL: Me query successful", { result });
11506
11626
  return true;
11507
11627
  } catch (e) {
11508
11628
  const error = e;
11509
- logDebug(`verify connection failed ${error.toString()}`);
11629
+ logDebug(`API connection verification failed ${error.toString()}`);
11510
11630
  if (error?.toString().includes("FetchError")) {
11511
- logError("verify connection failed", { error });
11631
+ logError("API connection verification failed", { error });
11512
11632
  return false;
11513
11633
  }
11514
11634
  }
@@ -11520,7 +11640,7 @@ var McpGQLClient = class {
11520
11640
  const result = await this.clientSdk.uploadS3BucketInfo({
11521
11641
  fileName: "report.json"
11522
11642
  });
11523
- logInfo("GraphQL: uploadS3BucketInfo successful", { result });
11643
+ logDebug("GraphQL: uploadS3BucketInfo successful", { result });
11524
11644
  return result;
11525
11645
  } catch (e) {
11526
11646
  logError("GraphQL: uploadS3BucketInfo failed", {
@@ -11536,7 +11656,7 @@ var McpGQLClient = class {
11536
11656
  const res = await this.clientSdk.getAnalysis({
11537
11657
  analysisId
11538
11658
  });
11539
- logInfo("GraphQL: getAnalysis successful", { result: res });
11659
+ logDebug("GraphQL: getAnalysis successful", { result: res });
11540
11660
  if (!res.analysis) {
11541
11661
  throw new Error(`Analysis not found: ${analysisId}`);
11542
11662
  }
@@ -11556,7 +11676,7 @@ var McpGQLClient = class {
11556
11676
  variables
11557
11677
  });
11558
11678
  const result = await this.clientSdk.SubmitVulnerabilityReport(variables);
11559
- logInfo("GraphQL: SubmitVulnerabilityReport successful", { result });
11679
+ logDebug("GraphQL: SubmitVulnerabilityReport successful", { result });
11560
11680
  return result;
11561
11681
  } catch (e) {
11562
11682
  logError("GraphQL: SubmitVulnerabilityReport failed", {
@@ -11590,7 +11710,7 @@ var McpGQLClient = class {
11590
11710
  return;
11591
11711
  }
11592
11712
  if (callbackStates.includes(data.analysis?.state)) {
11593
- logInfo("GraphQL: Analysis state matches callback states", {
11713
+ logDebug("GraphQL: Analysis state matches callback states", {
11594
11714
  analysisId: data.analysis.id,
11595
11715
  state: data.analysis.state,
11596
11716
  callbackStates
@@ -11609,7 +11729,7 @@ var McpGQLClient = class {
11609
11729
  timeoutInMs: params.timeoutInMs
11610
11730
  }
11611
11731
  );
11612
- logInfo("GraphQL: GetAnalysis subscription completed", { result });
11732
+ logDebug("GraphQL: GetAnalysis subscription completed", { result });
11613
11733
  return result;
11614
11734
  } catch (e) {
11615
11735
  logError("GraphQL: GetAnalysis subscription failed", {
@@ -11630,38 +11750,41 @@ var McpGQLClient = class {
11630
11750
  if (!userEmail) {
11631
11751
  throw new Error("User email not found");
11632
11752
  }
11633
- const shortEmailHash = crypto2.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
11753
+ const shortEmailHash = crypto3.createHash("sha256").update(userEmail).digest("hex").slice(0, 8).toUpperCase();
11634
11754
  const projectName = `MCP Scans ${shortEmailHash}`;
11635
- logDebug("GraphQL: Calling getOrgAndProjectId query", { projectName });
11636
- const getOrgAndProjectIdResult = await this.clientSdk.getOrgAndProjectId({
11637
- filters: {},
11638
- limit: 1
11755
+ logDebug("GraphQL: Calling getLastOrgAndNamedProject query", {
11756
+ projectName
11757
+ });
11758
+ const orgAndProjectRes = await this.clientSdk.getLastOrgAndNamedProject({
11759
+ email: userEmail,
11760
+ projectName
11639
11761
  });
11640
- logInfo("GraphQL: getOrgAndProjectId successful", {
11641
- result: getOrgAndProjectIdResult
11762
+ logDebug("GraphQL: getLastOrgAndNamedProject successful", {
11763
+ result: orgAndProjectRes
11642
11764
  });
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,
11765
+ if (!orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization?.id) {
11766
+ throw new Error(
11767
+ `The user with email:${userEmail} is not associated with any organization`
11768
+ );
11769
+ }
11770
+ const organization = orgAndProjectRes.user?.[0]?.userOrganizationsAndUserOrganizationRoles?.[0]?.organization;
11771
+ const projectId = organization?.projects?.[0]?.id;
11772
+ if (projectId) {
11773
+ logDebug("GraphQL: Found existing project", {
11774
+ projectId,
11652
11775
  projectName
11653
11776
  });
11654
- return project.id;
11777
+ return projectId;
11655
11778
  }
11656
11779
  logDebug("GraphQL: Project not found, creating new project", {
11657
- organizationId: org.id,
11780
+ organizationId: organization.id,
11658
11781
  projectName
11659
11782
  });
11660
11783
  const createdProject = await this.clientSdk.CreateProject({
11661
- organizationId: org.id,
11784
+ organizationId: organization.id,
11662
11785
  projectName
11663
11786
  });
11664
- logInfo("GraphQL: CreateProject successful", { result: createdProject });
11787
+ logDebug("GraphQL: CreateProject successful", { result: createdProject });
11665
11788
  return createdProject.createProject.projectId;
11666
11789
  } catch (e) {
11667
11790
  logError("GraphQL: getProjectId failed", {
@@ -11675,15 +11798,15 @@ var McpGQLClient = class {
11675
11798
  const { me } = await this.clientSdk.Me();
11676
11799
  return me;
11677
11800
  }
11678
- async verifyToken() {
11679
- logDebug("verifying token");
11801
+ async validateUserToken() {
11802
+ logDebug("validating user token");
11680
11803
  try {
11681
11804
  await this.clientSdk.CreateCommunityUser();
11682
11805
  const info = await this.getUserInfo();
11683
- logDebug("token verified");
11806
+ logDebug("user token validated successfully");
11684
11807
  return info?.email || true;
11685
11808
  } catch (e) {
11686
- logError("verify token failed");
11809
+ logError("user token validation failed");
11687
11810
  return false;
11688
11811
  }
11689
11812
  }
@@ -11716,18 +11839,34 @@ var McpGQLClient = class {
11716
11839
  return null;
11717
11840
  }
11718
11841
  }
11719
- async _updateFixesArchiveState(fixIds) {
11842
+ mergeUserAndSystemFixes(reportData, limit) {
11843
+ if (!reportData) return [];
11844
+ const { userFixes = [], fixes = [] } = reportData;
11845
+ const fixMap = /* @__PURE__ */ new Map();
11846
+ for (const fix of userFixes) {
11847
+ if (fix.id) {
11848
+ fixMap.set(fix.id, fix);
11849
+ }
11850
+ }
11851
+ for (const fix of fixes) {
11852
+ if (fix.id && !fixMap.has(fix.id)) {
11853
+ fixMap.set(fix.id, fix);
11854
+ }
11855
+ }
11856
+ return Array.from(fixMap.values()).slice(0, limit);
11857
+ }
11858
+ async updateFixesDownloadStatus(fixIds) {
11720
11859
  if (fixIds.length > 0) {
11721
11860
  const resUpdate = await this.clientSdk.updateDownloadedFixData({
11722
11861
  fixIds,
11723
11862
  source: "MCP" /* Mcp */
11724
11863
  });
11725
- logInfo("GraphQL: updateFixesArchiveState successful", {
11864
+ logDebug("GraphQL: updateFixesDownloadStatus successful", {
11726
11865
  result: resUpdate,
11727
11866
  fixIds
11728
11867
  });
11729
11868
  } else {
11730
- logInfo("GraphQL: No fixes found");
11869
+ logDebug("GraphQL: No fixes found to update download status");
11731
11870
  }
11732
11871
  }
11733
11872
  async getLatestReportByRepoUrl({
@@ -11741,19 +11880,35 @@ var McpGQLClient = class {
11741
11880
  limit,
11742
11881
  offset
11743
11882
  });
11883
+ let currentUserEmail = "%@%";
11884
+ try {
11885
+ const userInfo = await this.getUserInfo();
11886
+ if (userInfo?.email) {
11887
+ currentUserEmail = `%${userInfo.email}%`;
11888
+ }
11889
+ } catch (err) {
11890
+ logDebug("Failed to get user email, using default pattern", {
11891
+ error: err
11892
+ });
11893
+ }
11744
11894
  const res = await this.clientSdk.GetLatestReportByRepoUrl({
11745
11895
  repoUrl,
11746
11896
  limit,
11747
- offset
11897
+ offset,
11898
+ currentUserEmail
11748
11899
  });
11749
- logInfo("GraphQL: GetLatestReportByRepoUrl successful", {
11900
+ logDebug("GraphQL: GetLatestReportByRepoUrl successful", {
11750
11901
  result: res,
11751
11902
  reportCount: res.fixReport?.length || 0
11752
11903
  });
11753
- const fixIds = res.fixReport?.[0]?.fixes?.map((fix) => fix.id) || [];
11754
- await this._updateFixesArchiveState(fixIds);
11904
+ const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
11905
+ const fixIds = fixes.map((fix) => fix.id);
11906
+ await this.updateFixesDownloadStatus(fixIds);
11755
11907
  return {
11756
- fixReport: res.fixReport?.[0] || null,
11908
+ fixReport: res.fixReport?.[0] ? {
11909
+ ...res.fixReport?.[0],
11910
+ fixes
11911
+ } : null,
11757
11912
  expiredReport: res.expiredReport?.[0] || null
11758
11913
  };
11759
11914
  } catch (e) {
@@ -11772,14 +11927,14 @@ var McpGQLClient = class {
11772
11927
  issueType,
11773
11928
  severity
11774
11929
  }) {
11930
+ const filters = {};
11931
+ if (issueType && issueType.length > 0) {
11932
+ filters["safeIssueType"] = { _in: issueType };
11933
+ }
11934
+ if (severity && severity.length > 0) {
11935
+ filters["severityText"] = { _in: severity };
11936
+ }
11775
11937
  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
11938
  logDebug("GraphQL: Calling GetReportFixes query", {
11784
11939
  reportId,
11785
11940
  limit,
@@ -11788,13 +11943,25 @@ var McpGQLClient = class {
11788
11943
  issueType,
11789
11944
  severity
11790
11945
  });
11946
+ let currentUserEmail = "%@%";
11947
+ try {
11948
+ const userInfo = await this.getUserInfo();
11949
+ if (userInfo?.email) {
11950
+ currentUserEmail = `%${userInfo.email}%`;
11951
+ }
11952
+ } catch (err) {
11953
+ logDebug("Failed to get user email, using default pattern", {
11954
+ error: err
11955
+ });
11956
+ }
11791
11957
  const res = await this.clientSdk.GetReportFixes({
11792
11958
  reportId,
11793
11959
  limit,
11794
11960
  offset,
11795
- filters
11961
+ filters,
11962
+ currentUserEmail
11796
11963
  });
11797
- logInfo("GraphQL: GetReportFixes successful", {
11964
+ logDebug("GraphQL: GetReportFixes successful", {
11798
11965
  result: res,
11799
11966
  fixCount: res.fixReport?.[0]?.fixes?.length || 0,
11800
11967
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0
@@ -11802,10 +11969,11 @@ var McpGQLClient = class {
11802
11969
  if (res.fixReport.length === 0) {
11803
11970
  return null;
11804
11971
  }
11805
- const fixIds = res.fixReport?.[0]?.fixes?.map((fix) => fix.id) || [];
11806
- await this._updateFixesArchiveState(fixIds);
11972
+ const fixes = this.mergeUserAndSystemFixes(res.fixReport?.[0], limit);
11973
+ const fixIds = fixes.map((fix) => fix.id);
11974
+ await this.updateFixesDownloadStatus(fixIds);
11807
11975
  return {
11808
- fixes: res.fixReport?.[0]?.fixes || [],
11976
+ fixes,
11809
11977
  totalCount: res.fixReport?.[0]?.filteredFixesCount?.aggregate?.count || 0,
11810
11978
  expiredReport: res.expiredReport?.[0] || null
11811
11979
  };
@@ -11819,82 +11987,29 @@ var McpGQLClient = class {
11819
11987
  }
11820
11988
  }
11821
11989
  };
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
11990
+ async function createAuthenticatedMcpGQLClient({
11991
+ isBackgoundCall = false
11837
11992
  } = {}) {
11838
11993
  logDebug("getting config", { apiToken: mobbConfigStore.get("apiToken") });
11839
- const inGqlClient = new McpGQLClient({
11994
+ const initialClient = new McpGQLClient({
11840
11995
  apiKey: process.env["MOBB_API_KEY"] || process.env["API_KEY"] || // fallback for backward compatibility
11841
11996
  mobbConfigStore.get("apiToken") || "",
11842
11997
  type: "apiKey"
11843
11998
  });
11844
- const isConnected = await inGqlClient.verifyConnection();
11845
- logDebug("isConnected", { isConnected });
11999
+ const isConnected = await initialClient.verifyApiConnection();
12000
+ logDebug("API connection status", { isConnected });
11846
12001
  if (!isConnected) {
11847
12002
  throw new ApiConnectionError("Error: failed to connect to Mobb API");
11848
12003
  }
11849
- logDebug("verifying token");
11850
- const userVerify = await inGqlClient.verifyToken();
12004
+ logDebug("validating user token");
12005
+ const userVerify = await initialClient.validateUserToken();
11851
12006
  if (userVerify) {
11852
- return inGqlClient;
11853
- }
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");
12007
+ return initialClient;
11896
12008
  }
11897
- return newGqlClient;
12009
+ const authService = new McpAuthService(initialClient);
12010
+ const newApiToken = await authService.authenticate(isBackgoundCall);
12011
+ mobbConfigStore.set("apiToken", newApiToken);
12012
+ return new McpGQLClient({ apiKey: newApiToken, type: "apiKey" });
11898
12013
  }
11899
12014
 
11900
12015
  // src/mcp/core/ToolRegistry.ts
@@ -11951,14 +12066,11 @@ var McpServer = class {
11951
12066
  this.toolRegistry = new ToolRegistry();
11952
12067
  this.setupHandlers();
11953
12068
  this.setupProcessEventHandlers();
11954
- logInfo("MCP server instance created", config4);
12069
+ logInfo("MCP server instance created");
12070
+ logDebug("MCP server instance config", { config: config4 });
11955
12071
  }
11956
- setupProcessEventHandlers() {
11957
- if (this.isEventHandlersSetup) {
11958
- logDebug("Process event handlers already setup, skipping");
11959
- return;
11960
- }
11961
- const signals = {
12072
+ handleProcessSignal(signal, error) {
12073
+ const messages = {
11962
12074
  SIGINT: "MCP server interrupted",
11963
12075
  SIGTERM: "MCP server terminated",
11964
12076
  exit: "MCP server exiting",
@@ -11966,26 +12078,46 @@ var McpServer = class {
11966
12078
  unhandledRejection: "Unhandled promise rejection in MCP server",
11967
12079
  warning: "Warning in MCP server"
11968
12080
  };
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
- );
12081
+ const message = messages[signal] || `Unhandled signal: ${signal}`;
12082
+ if (signal === "exit") {
12083
+ const exitCode = error;
12084
+ if (exitCode === 0 || exitCode === void 0) {
12085
+ logDebug(`${message} (clean exit)`, { signal, exitCode });
12086
+ } else {
12087
+ logWarn(`${message} (exit code: ${exitCode})`, { signal, exitCode });
12088
+ }
12089
+ } else if (error) {
12090
+ logError(`${message}`, { error, signal });
12091
+ } else {
12092
+ logDebug(message, { signal });
12093
+ }
12094
+ if (signal === "SIGINT" || signal === "SIGTERM") {
12095
+ process.exit(0);
12096
+ }
12097
+ if (signal === "uncaughtException") {
12098
+ process.exit(1);
12099
+ }
12100
+ }
12101
+ setupProcessEventHandlers() {
12102
+ if (this.isEventHandlersSetup) {
12103
+ logDebug("Process event handlers already setup, skipping");
12104
+ return;
12105
+ }
12106
+ const signals = [
12107
+ "SIGINT",
12108
+ "SIGTERM",
12109
+ "exit",
12110
+ "uncaughtException",
12111
+ "unhandledRejection",
12112
+ "warning"
12113
+ ];
12114
+ signals.forEach((signal) => {
12115
+ process.on(signal, (error) => {
12116
+ this.handleProcessSignal(signal, error);
12117
+ });
11986
12118
  });
11987
12119
  this.isEventHandlersSetup = true;
11988
- logDebug("Process event handlers registered");
12120
+ logInfo("Process event handlers registered");
11989
12121
  }
11990
12122
  createShutdownPromise() {
11991
12123
  return new Promise((resolve) => {
@@ -11997,12 +12129,45 @@ var McpServer = class {
11997
12129
  process.once("SIGTERM", cleanup);
11998
12130
  });
11999
12131
  }
12132
+ async triggerScanForNewAvailableFixes() {
12133
+ const gqlClient = await createAuthenticatedMcpGQLClient({
12134
+ isBackgoundCall: true
12135
+ });
12136
+ const isConnected = await gqlClient.verifyApiConnection();
12137
+ if (!isConnected) {
12138
+ logError("Failed to connect to the API, skipping scan");
12139
+ return;
12140
+ }
12141
+ if (process.env["WORKSPACE_FOLDER_PATHS"]) {
12142
+ logDebug("WORKSPACE_FOLDER_PATHS is set", {
12143
+ WORKSPACE_FOLDER_PATHS: process.env["WORKSPACE_FOLDER_PATHS"]
12144
+ });
12145
+ try {
12146
+ const checkForNewAvailableFixesTool = this.toolRegistry.getTool(
12147
+ MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES
12148
+ );
12149
+ logInfo("Triggering periodic scan for new available fixes");
12150
+ checkForNewAvailableFixesTool.triggerScan({
12151
+ path: process.env["WORKSPACE_FOLDER_PATHS"],
12152
+ gqlClient
12153
+ });
12154
+ } catch (error) {
12155
+ logError("Error getting workspace folder path tool", { error });
12156
+ }
12157
+ }
12158
+ }
12000
12159
  async handleListToolsRequest(request) {
12001
- logInfo("Received list_tools request", { params: request.params });
12002
- logInfo("Request", {
12160
+ logInfo("Received list_tools request");
12161
+ logDebug("list_tools request", {
12162
+ request: JSON.parse(JSON.stringify(request))
12163
+ });
12164
+ logDebug("Request", {
12003
12165
  request: JSON.parse(JSON.stringify(request))
12004
12166
  });
12005
- void getMcpGQLClient({ isToolsCall: true });
12167
+ logDebug("env", {
12168
+ env: process.env
12169
+ });
12170
+ void this.triggerScanForNewAvailableFixes();
12006
12171
  const toolsDefinitions = this.toolRegistry.getAllTools();
12007
12172
  const response = {
12008
12173
  tools: toolsDefinitions.map((tool) => ({
@@ -12016,13 +12181,13 @@ var McpServer = class {
12016
12181
  }
12017
12182
  }))
12018
12183
  };
12019
- logInfo("Returning list_tools response", { response });
12184
+ logDebug("Returning list_tools response", { response });
12020
12185
  return response;
12021
12186
  }
12022
12187
  async handleCallToolRequest(request) {
12023
12188
  const { name, arguments: args } = request.params;
12024
- logInfo(`Received call tool request for ${name}`, { name, args });
12025
- logInfo("Request", {
12189
+ logInfo(`Received call tool request for ${name}`);
12190
+ logDebug("Request", {
12026
12191
  request: JSON.parse(JSON.stringify(request))
12027
12192
  });
12028
12193
  try {
@@ -12035,10 +12200,11 @@ var McpServer = class {
12035
12200
  });
12036
12201
  throw new Error(errorMsg);
12037
12202
  }
12038
- logDebug(`Executing tool: ${name}`, { args });
12203
+ logInfo(`Executing tool: ${name}`);
12039
12204
  const response = await tool.execute(args);
12040
12205
  const serializedResponse = JSON.parse(JSON.stringify(response));
12041
- logInfo(`Tool ${name} executed successfully`, {
12206
+ logInfo(`Tool ${name} executed successfully`);
12207
+ logDebug(`Tool ${name} executed successfully`, {
12042
12208
  responseType: typeof response,
12043
12209
  hasContent: !!serializedResponse.content
12044
12210
  });
@@ -12062,18 +12228,18 @@ var McpServer = class {
12062
12228
  CallToolRequestSchema,
12063
12229
  (request) => this.handleCallToolRequest(request)
12064
12230
  );
12065
- logDebug("MCP server handlers registered");
12231
+ logInfo("MCP server handlers registered");
12066
12232
  }
12067
12233
  registerTool(tool) {
12068
12234
  this.toolRegistry.registerTool(tool);
12069
- logDebug(`Tool registered: ${tool.name}`);
12235
+ logInfo(`Tool registered: ${tool.name}`);
12070
12236
  }
12071
12237
  async start() {
12072
12238
  try {
12073
- logDebug("Starting MCP server");
12239
+ logInfo("Starting MCP server");
12074
12240
  const transport = new StdioServerTransport();
12075
12241
  await this.server.connect(transport);
12076
- logInfo("MCP server is running on stdin/stdout");
12242
+ logDebug("MCP server is running on stdin/stdout");
12077
12243
  process.stdin.resume();
12078
12244
  await this.createShutdownPromise();
12079
12245
  await this.stop();
@@ -12083,7 +12249,7 @@ var McpServer = class {
12083
12249
  }
12084
12250
  }
12085
12251
  async stop() {
12086
- logInfo("MCP server shutting down");
12252
+ logDebug("MCP server shutting down");
12087
12253
  }
12088
12254
  };
12089
12255
 
@@ -12168,14 +12334,15 @@ var BaseTool = class {
12168
12334
  };
12169
12335
  }
12170
12336
  async execute(args) {
12171
- logInfo(`Authenticating tool: ${this.name}`, { args });
12172
- const mcpGqlClient = await getMcpGQLClient();
12337
+ logDebug(`Authenticating tool: ${this.name}`, { args });
12338
+ const mcpGqlClient = await createAuthenticatedMcpGQLClient();
12173
12339
  const userInfo = await mcpGqlClient.getUserInfo();
12174
- logDebug("Authenticated", { userInfo });
12340
+ logDebug("User authenticated successfully", { userInfo });
12175
12341
  const validatedArgs = this.validateInput(args);
12176
12342
  logDebug(`Tool ${this.name} input validation successful`, {
12177
12343
  validatedArgs
12178
12344
  });
12345
+ logInfo(`Executing tool: ${this.name}`);
12179
12346
  const result = await this.executeInternal(validatedArgs);
12180
12347
  logInfo(`Tool ${this.name} executed successfully`);
12181
12348
  return result;
@@ -12209,7 +12376,7 @@ var BaseTool = class {
12209
12376
  };
12210
12377
 
12211
12378
  // src/mcp/core/prompts.ts
12212
- function frienlyType(s) {
12379
+ function friendlyType(s) {
12213
12380
  const withoutUnderscores = s.replace(/_/g, " ");
12214
12381
  const result = withoutUnderscores.replace(/([a-z])([A-Z])/g, "$1 $2");
12215
12382
  return result.charAt(0).toUpperCase() + result.slice(1);
@@ -12229,13 +12396,15 @@ var applyFixesPrompt = ({
12229
12396
  return noFixesReturnedForParameters;
12230
12397
  }
12231
12398
  const fixList = fixes.map((fix) => {
12232
- const vulnerabilityType = frienlyType(fix.safeIssueType);
12399
+ const vulnerabilityType = friendlyType(fix.safeIssueType);
12233
12400
  const vulnerabilityDescription = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.extraContext?.fixDescription : void 0;
12234
12401
  const patch = fix.patchAndQuestions?.__typename === "FixData" ? fix.patchAndQuestions.patch : void 0;
12402
+ const gitBlameLogin = fix.gitBlameLogin;
12235
12403
  return {
12236
12404
  vulnerabilityType,
12237
12405
  vulnerabilityDescription,
12238
- patch
12406
+ patch,
12407
+ gitBlameLogin
12239
12408
  };
12240
12409
  });
12241
12410
  return `## CRITICAL INSTRUCTIONS - READ CAREFULLY
@@ -12287,7 +12456,9 @@ ${fixList.map(
12287
12456
 
12288
12457
  **\u{1F4DD} Description:** ${fix.vulnerabilityDescription || "Security vulnerability fix"}
12289
12458
 
12290
- **\u{1F527} Action Required:** Apply the following patch exactly as shown
12459
+ ${fix.gitBlameLogin ? `**\u{1F464} Git Blame:** The code that needs to be fixed was last modified by: \`${fix.gitBlameLogin}\`
12460
+
12461
+ ` : ""}**\u{1F527} Action Required:** Apply the following patch exactly as shown
12291
12462
 
12292
12463
  **\u{1F4C1} Patch to Apply:**
12293
12464
  \`\`\`diff
@@ -12334,7 +12505,7 @@ We were unable to find a previous vulnerability report for this repository. This
12334
12505
 
12335
12506
  ### \u{1F3AF} Recommended Actions
12336
12507
  1. **Run a new security scan** to analyze your codebase
12337
- - Use the \`scan_and_fix_vulnerabilities\` tool to start a fresh scan
12508
+ - Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool to start a fresh scan
12338
12509
  - This will analyze your current code for security issues
12339
12510
 
12340
12511
  2. **Verify repository access**
@@ -12362,7 +12533,7 @@ Your most recent vulnerability report for this repository **expired on ${lastRep
12362
12533
 
12363
12534
  ### \u{1F3AF} Recommended Actions
12364
12535
  1. **Run a fresh security scan** to generate an up-to-date vulnerability report.
12365
- - Use the \`scan_and_fix_vulnerabilities\` tool.
12536
+ - Use the \`${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}\` tool.
12366
12537
  2. **Verify repository access** if scans fail to run or the repository has moved.
12367
12538
  3. **Review your CI/CD pipeline** to ensure regular scans are triggered.
12368
12539
 
@@ -12416,7 +12587,7 @@ ${applyFixesPrompt({
12416
12587
  hasMore,
12417
12588
  nextOffset: 0,
12418
12589
  shownCount: fixReport.fixes.length,
12419
- currentTool: "fetch_available_fixes",
12590
+ currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
12420
12591
  offset
12421
12592
  })}`;
12422
12593
  };
@@ -12441,7 +12612,6 @@ To perform a **rescan** of your repository (fetching a brand-new vulnerability r
12441
12612
  This will start a new analysis, discard any cached results.
12442
12613
 
12443
12614
  \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
12615
  `;
12446
12616
  var noFixesFoundPrompt = ({
12447
12617
  scannedFiles
@@ -12467,20 +12637,19 @@ var fixesPrompt = ({
12467
12637
 
12468
12638
  ${applyFixesPrompt({
12469
12639
  fixes,
12470
- totalCount,
12471
12640
  hasMore,
12641
+ totalCount,
12472
12642
  nextOffset,
12473
12643
  shownCount,
12474
- currentTool: "scan_and_fix_vulnerabilities",
12644
+ currentTool: MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES,
12475
12645
  offset
12476
12646
  })}
12477
12647
 
12478
12648
  ${nextStepsPrompt({ scannedFiles })}
12479
12649
  `;
12480
12650
  };
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.`;
12651
+ var noFreshFixesPrompt = `No fresh fixes available for this repository at this time. No user message needed. Do not show any comments to the user.`;
12652
+ var initialScanInProgressPrompt = `Initial scan is still in progress. No user message needed. Do not show any comments to the user.`;
12484
12653
  var freshFixesPrompt = ({ fixes }) => {
12485
12654
  return `Here are the fresh fixes to the vulnerabilities discovered by Mobb MCP
12486
12655
 
@@ -12490,7 +12659,7 @@ ${applyFixesPrompt({
12490
12659
  hasMore: false,
12491
12660
  nextOffset: 0,
12492
12661
  shownCount: fixes.length,
12493
- currentTool: "fetch_available_fixes",
12662
+ currentTool: MCP_TOOL_FETCH_AVAILABLE_FIXES,
12494
12663
  offset: 0
12495
12664
  })}
12496
12665
  `;
@@ -12579,23 +12748,135 @@ var getLocalFiles = async ({
12579
12748
  return filesWithStats.filter((file) => file.lastEdited > 0);
12580
12749
  };
12581
12750
 
12582
- // src/mcp/services/ScanFiles.ts
12751
+ // src/mcp/services/FileOperations.ts
12583
12752
  import fs11 from "fs";
12584
12753
  import path12 from "path";
12585
12754
  import AdmZip2 from "adm-zip";
12755
+ var FileOperations = class {
12756
+ /**
12757
+ * Creates a ZIP archive containing the specified source files
12758
+ * @param fileList Array of relative file paths to include
12759
+ * @param repositoryPath Base path for resolving relative file paths
12760
+ * @param maxFileSize Maximum size allowed for individual files
12761
+ * @returns ZIP archive as a Buffer with metadata
12762
+ */
12763
+ async createSourceCodeArchive(fileList, repositoryPath, maxFileSize) {
12764
+ logDebug("FilePacking: packing files");
12765
+ const zip = new AdmZip2();
12766
+ let packedFilesCount = 0;
12767
+ const resolvedRepoPath = path12.resolve(repositoryPath);
12768
+ for (const filepath of fileList) {
12769
+ const absoluteFilepath = path12.join(repositoryPath, filepath);
12770
+ const resolvedFilePath = path12.resolve(absoluteFilepath);
12771
+ if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
12772
+ logDebug(
12773
+ `Skipping ${filepath} due to potential path traversal security risk`
12774
+ );
12775
+ continue;
12776
+ }
12777
+ if (!FileUtils.shouldPackFile(absoluteFilepath, maxFileSize)) {
12778
+ logDebug(
12779
+ `Excluding ${filepath} - file is too large, binary, or matches exclusion rules`
12780
+ );
12781
+ continue;
12782
+ }
12783
+ const fileContent = await this.readSourceFile(absoluteFilepath, filepath);
12784
+ if (fileContent) {
12785
+ zip.addFile(filepath, fileContent);
12786
+ packedFilesCount++;
12787
+ }
12788
+ }
12789
+ const archiveBuffer = zip.toBuffer();
12790
+ const result = {
12791
+ archive: archiveBuffer,
12792
+ packedFilesCount,
12793
+ totalSize: archiveBuffer.length
12794
+ };
12795
+ logInfo("Files packed successfully");
12796
+ return result;
12797
+ }
12798
+ /**
12799
+ * Validates that file paths are within the repository and safe to access
12800
+ * @param fileList Array of relative file paths to validate
12801
+ * @param repositoryPath Base path for validation
12802
+ * @returns Array of validated file paths
12803
+ */
12804
+ async validateFilePaths(fileList, repositoryPath) {
12805
+ const resolvedRepoPath = path12.resolve(repositoryPath);
12806
+ const validatedPaths = [];
12807
+ for (const filepath of fileList) {
12808
+ const absoluteFilepath = path12.join(repositoryPath, filepath);
12809
+ const resolvedFilePath = path12.resolve(absoluteFilepath);
12810
+ if (!resolvedFilePath.startsWith(resolvedRepoPath)) {
12811
+ logDebug(`Rejecting ${filepath} - path traversal attempt detected`);
12812
+ continue;
12813
+ }
12814
+ try {
12815
+ await fs11.promises.access(absoluteFilepath, fs11.constants.R_OK);
12816
+ validatedPaths.push(filepath);
12817
+ } catch (error) {
12818
+ logDebug(`Skipping ${filepath} - file is not accessible: ${error}`);
12819
+ }
12820
+ }
12821
+ return validatedPaths;
12822
+ }
12823
+ /**
12824
+ * Reads source files and returns their data
12825
+ * @param filePaths Array of absolute file paths to read
12826
+ * @returns Array of file data objects
12827
+ */
12828
+ async readSourceFiles(filePaths) {
12829
+ const fileDataArray = [];
12830
+ for (const absolutePath of filePaths) {
12831
+ try {
12832
+ const content = await fs11.promises.readFile(absolutePath);
12833
+ const relativePath = path12.basename(absolutePath);
12834
+ fileDataArray.push({
12835
+ relativePath,
12836
+ absolutePath,
12837
+ content
12838
+ });
12839
+ } catch (error) {
12840
+ logError(`Failed to read file ${absolutePath}: ${error}`);
12841
+ }
12842
+ }
12843
+ return fileDataArray;
12844
+ }
12845
+ /**
12846
+ * Safely reads a single source file
12847
+ * @param absoluteFilepath Absolute path to the file
12848
+ * @param relativeFilepath Relative path for logging purposes
12849
+ * @returns File content as Buffer or null if failed
12850
+ */
12851
+ async readSourceFile(absoluteFilepath, relativeFilepath) {
12852
+ try {
12853
+ return await fs11.promises.readFile(absoluteFilepath);
12854
+ } catch (fsError) {
12855
+ logError(`Failed to read ${relativeFilepath} from filesystem: ${fsError}`);
12856
+ return null;
12857
+ }
12858
+ }
12859
+ };
12860
+
12861
+ // src/mcp/services/ScanFiles.ts
12586
12862
  var scanFiles = async (fileList, repositoryPath, gqlClient) => {
12587
- const repoUploadInfo = await initializeReport(gqlClient);
12863
+ const repoUploadInfo = await initializeSecurityReport(gqlClient);
12588
12864
  const fixReportId = repoUploadInfo.fixReportId;
12589
- const zipBuffer = await packFiles(fileList, repositoryPath);
12590
- await uploadFiles(zipBuffer, repoUploadInfo);
12865
+ const fileOperations = new FileOperations();
12866
+ const packingResult = await fileOperations.createSourceCodeArchive(
12867
+ fileList,
12868
+ repositoryPath,
12869
+ MCP_MAX_FILE_SIZE
12870
+ );
12871
+ await uploadSourceCodeArchive(packingResult.archive, repoUploadInfo);
12591
12872
  const projectId = await getProjectId(gqlClient);
12592
- await runScan({ fixReportId, projectId, gqlClient });
12873
+ await executeSecurityScan({ fixReportId, projectId, gqlClient });
12593
12874
  return {
12594
12875
  fixReportId,
12595
12876
  projectId
12596
12877
  };
12597
12878
  };
12598
- var initializeReport = async (gqlClient) => {
12879
+ var initializeSecurityReport = async (gqlClient) => {
12599
12880
  if (!gqlClient) {
12600
12881
  throw new GqlClientError();
12601
12882
  }
@@ -12603,74 +12884,33 @@ var initializeReport = async (gqlClient) => {
12603
12884
  const {
12604
12885
  uploadS3BucketInfo: { repoUploadInfo }
12605
12886
  } = await gqlClient.uploadS3BucketInfo();
12606
- logInfo("Upload info retrieved", { uploadKey: repoUploadInfo?.uploadKey });
12887
+ logDebug("Upload info retrieved");
12607
12888
  return repoUploadInfo;
12608
12889
  } catch (error) {
12609
12890
  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`
12891
+ throw new ReportInitializationError(
12892
+ `Error initializing security report: ${message}`
12650
12893
  );
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
12894
  }
12657
12895
  };
12658
- var uploadFiles = async (zipBuffer, repoUploadInfo) => {
12896
+ var uploadSourceCodeArchive = async (archiveBuffer, repoUploadInfo) => {
12659
12897
  if (!repoUploadInfo) {
12660
- throw new FileUploadError("Upload info is required");
12898
+ throw new FileUploadError("Upload info is required for source code archive");
12661
12899
  }
12662
12900
  try {
12663
12901
  await uploadFile({
12664
- file: zipBuffer,
12902
+ file: archiveBuffer,
12665
12903
  url: repoUploadInfo.url,
12666
12904
  uploadFields: JSON.parse(repoUploadInfo.uploadFieldsJSON),
12667
12905
  uploadKey: repoUploadInfo.uploadKey
12668
12906
  });
12669
12907
  logInfo("File uploaded successfully");
12670
12908
  } catch (error) {
12671
- logError("File upload failed", { error: error.message });
12909
+ logError("Source code archive upload failed", {
12910
+ error: error.message
12911
+ });
12672
12912
  throw new FileUploadError(
12673
- `Failed to upload the file: ${error.message}`
12913
+ `Failed to upload source code archive: ${error.message}`
12674
12914
  );
12675
12915
  }
12676
12916
  };
@@ -12679,10 +12919,10 @@ var getProjectId = async (gqlClient) => {
12679
12919
  throw new GqlClientError();
12680
12920
  }
12681
12921
  const projectId = await gqlClient.getProjectId();
12682
- logInfo("Project ID retrieved", { projectId });
12922
+ logDebug("Project ID retrieved");
12683
12923
  return projectId;
12684
12924
  };
12685
- var runScan = async ({
12925
+ var executeSecurityScan = async ({
12686
12926
  fixReportId,
12687
12927
  projectId,
12688
12928
  gqlClient
@@ -12690,7 +12930,7 @@ var runScan = async ({
12690
12930
  if (!gqlClient) {
12691
12931
  throw new GqlClientError();
12692
12932
  }
12693
- logInfo("Starting scan", { fixReportId, projectId });
12933
+ logInfo("Starting scan");
12694
12934
  const submitVulnerabilityReportVariables = {
12695
12935
  fixReportId,
12696
12936
  projectId,
@@ -12703,25 +12943,28 @@ var runScan = async ({
12703
12943
  submitVulnerabilityReportVariables
12704
12944
  );
12705
12945
  if (submitRes.submitVulnerabilityReport.__typename !== "VulnerabilityReport") {
12706
- logError("Vulnerability report submission failed", {
12707
- response: submitRes
12946
+ throw new ScanError(
12947
+ `Security scan submission failed: ${submitRes.submitVulnerabilityReport.__typename}`
12948
+ );
12949
+ }
12950
+ const analysisId = submitRes.submitVulnerabilityReport.fixReportId;
12951
+ logInfo("Vulnerability report submitted successfully");
12952
+ try {
12953
+ await gqlClient.subscribeToGetAnalysis({
12954
+ subscribeToAnalysisParams: { analysisId },
12955
+ callback: async (completedAnalysisId) => {
12956
+ logInfo("Security analysis completed successfully", {
12957
+ analysisId: completedAnalysisId
12958
+ });
12959
+ },
12960
+ callbackStates: ["Finished" /* Finished */],
12961
+ timeoutInMs: MCP_VUL_REPORT_DIGEST_TIMEOUT_MS
12708
12962
  });
12709
- throw new ScanError("\u{1F575}\uFE0F\u200D\u2642\uFE0F Mobb analysis failed");
12963
+ } catch (error) {
12964
+ logError("Security analysis failed or timed out", { error, analysisId });
12965
+ throw new ScanError(`Security analysis failed: ${error.message}`);
12710
12966
  }
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");
12967
+ logDebug("Security scan completed successfully", { fixReportId, projectId });
12725
12968
  };
12726
12969
 
12727
12970
  // src/mcp/tools/checkForNewAvailableFixes/CheckForNewAvailableFixesService.ts
@@ -12742,6 +12985,7 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12742
12985
  __publicField(this, "reportedFixes", []);
12743
12986
  __publicField(this, "intervalId", null);
12744
12987
  __publicField(this, "isInitialScanComplete", false);
12988
+ __publicField(this, "gqlClient", null);
12745
12989
  }
12746
12990
  static getInstance() {
12747
12991
  if (!_CheckForNewAvailableFixesService.instance) {
@@ -12763,25 +13007,28 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12763
13007
  }
12764
13008
  }
12765
13009
  /**
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.
13010
+ * Scans repository for security vulnerabilities and identifies new fixes
13011
+ * since the last scan.
12770
13012
  */
12771
- async scan({ path: path13 }) {
12772
- logInfo("Scanning for new fixes", { path: path13 });
12773
- const gqlClient = await getMcpGQLClient();
12774
- const isConnected = await gqlClient.verifyConnection();
13013
+ async scanForSecurityVulnerabilities({
13014
+ path: path13
13015
+ }) {
13016
+ logDebug("Scanning for new security vulnerabilities", { path: path13 });
13017
+ if (!this.gqlClient) {
13018
+ logInfo("No GQL client found, skipping scan");
13019
+ return;
13020
+ }
13021
+ const isConnected = await this.gqlClient.verifyApiConnection();
12775
13022
  if (!isConnected) {
12776
13023
  logError("Failed to connect to the API, scan aborted");
12777
13024
  return;
12778
13025
  }
12779
- logInfo("Connected to the API, assebling list of files to scan", { path: path13 });
13026
+ logDebug("Connected to the API, assembling list of files to scan", { path: path13 });
12780
13027
  const files = await getLocalFiles({
12781
13028
  path: path13,
12782
13029
  maxFileSize: MCP_MAX_FILE_SIZE
12783
13030
  });
12784
- logInfo("Active files", { files });
13031
+ logDebug("Active files", { files });
12785
13032
  const filesToScan = files.filter((file) => {
12786
13033
  const lastScannedEditTime = this.filesLastScanned[file.fullPath];
12787
13034
  if (!lastScannedEditTime) {
@@ -12790,34 +13037,45 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12790
13037
  return file.lastEdited > lastScannedEditTime;
12791
13038
  });
12792
13039
  if (filesToScan.length === 0) {
12793
- logInfo("No files to scan", { path: path13 });
13040
+ logInfo("No files require scanning");
12794
13041
  return;
12795
13042
  }
12796
- logInfo("Files to scan", { filesToScan });
13043
+ logDebug("Files requiring security scan", { filesToScan });
12797
13044
  const { fixReportId, projectId } = await scanFiles(
12798
13045
  filesToScan.map((file) => file.relativePath),
12799
13046
  path13,
12800
- gqlClient
13047
+ this.gqlClient
13048
+ );
13049
+ logInfo(
13050
+ `Security scan completed for ${path13} reportId: ${fixReportId} projectId: ${projectId}`
12801
13051
  );
12802
- logInfo("Scan completed", { fixReportId, projectId });
12803
- const fixes = await gqlClient.getReportFixesPaginated({
13052
+ const fixes = await this.gqlClient.getReportFixesPaginated({
12804
13053
  reportId: fixReportId,
12805
13054
  offset: 0,
12806
13055
  limit: 1e3
12807
13056
  });
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
13057
+ const newFixes = fixes?.fixes?.filter(
13058
+ (fix) => !this.isFixAlreadyReported(fix)
13059
+ );
13060
+ logInfo(
13061
+ `Security fixes retrieved, total: ${fixes?.fixes?.length || 0}, new: ${newFixes?.length || 0}`
13062
+ );
13063
+ this.updateFreshFixesCache(newFixes || [], filesToScan);
13064
+ this.updateFilesScanTimestamps(filesToScan);
13065
+ this.isInitialScanComplete = true;
13066
+ }
13067
+ updateFreshFixesCache(newFixes, filesToScan) {
13068
+ this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes).sort((a, b) => {
13069
+ return (b.severityValue ?? 0) - (a.severityValue ?? 0);
12812
13070
  });
12813
- this.freshFixes = this.freshFixes.filter((fix) => !this.isFixFromOldScan(fix, filesToScan)).concat(newFixes || []);
12814
- logInfo("Fresh fixes", { freshFixes: this.freshFixes });
13071
+ logInfo(`Fresh fixes cache updated, total: ${this.freshFixes.length}`);
13072
+ }
13073
+ updateFilesScanTimestamps(filesToScan) {
12815
13074
  filesToScan.forEach((file) => {
12816
13075
  this.filesLastScanned[file.fullPath] = file.lastEdited;
12817
13076
  });
12818
- this.isInitialScanComplete = true;
12819
13077
  }
12820
- isAlreadyReported(fix) {
13078
+ isFixAlreadyReported(fix) {
12821
13079
  return this.reportedFixes.some(
12822
13080
  (reportedFix) => reportedFix.sharedState?.id === fix.sharedState?.id
12823
13081
  );
@@ -12828,10 +13086,10 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12828
13086
  if (!fixFile) {
12829
13087
  return false;
12830
13088
  }
12831
- logInfo("isOldFix", {
13089
+ logDebug("Checking if fix is from old scan", {
12832
13090
  fixFile,
12833
13091
  filesToScan,
12834
- isOldFix: filesToScan.some((file) => file.relativePath === fixFile)
13092
+ isFromOldScan: filesToScan.some((file) => file.relativePath === fixFile)
12835
13093
  });
12836
13094
  return filesToScan.some((file) => file.relativePath === fixFile);
12837
13095
  }
@@ -12840,31 +13098,51 @@ var _CheckForNewAvailableFixesService = class _CheckForNewAvailableFixesService
12840
13098
  this.path = path13;
12841
13099
  this.reset();
12842
13100
  }
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
- }
13101
+ this.gqlClient = await createAuthenticatedMcpGQLClient();
13102
+ this.triggerScan({ path: path13, gqlClient: this.gqlClient });
12856
13103
  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
- }
13104
+ return this.generateFreshFixesResponse();
12862
13105
  }
12863
13106
  if (!this.isInitialScanComplete) {
12864
13107
  return initialScanInProgressPrompt;
12865
13108
  }
12866
13109
  return noFreshFixesPrompt;
12867
13110
  }
13111
+ triggerScan({
13112
+ path: path13,
13113
+ gqlClient
13114
+ }) {
13115
+ this.gqlClient = gqlClient;
13116
+ if (!this.intervalId) {
13117
+ this.startPeriodicScanning(path13);
13118
+ this.executeInitialScan(path13);
13119
+ }
13120
+ }
13121
+ startPeriodicScanning(path13) {
13122
+ logDebug("Starting periodic scan for new security vulnerabilities", {
13123
+ path: path13
13124
+ });
13125
+ this.intervalId = setInterval(() => {
13126
+ logDebug("Triggering periodic security scan", { path: path13 });
13127
+ this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
13128
+ logError("Error during periodic security scan", { error });
13129
+ });
13130
+ }, MCP_PERIODIC_CHECK_INTERVAL);
13131
+ }
13132
+ executeInitialScan(path13) {
13133
+ logDebug("Triggering initial security scan", { path: path13 });
13134
+ this.scanForSecurityVulnerabilities({ path: path13 }).catch((error) => {
13135
+ logError("Error during initial security scan", { error });
13136
+ });
13137
+ }
13138
+ generateFreshFixesResponse() {
13139
+ const freshFixes = this.freshFixes.splice(0, 3);
13140
+ if (freshFixes.length > 0) {
13141
+ this.reportedFixes.push(...freshFixes);
13142
+ return freshFixesPrompt({ fixes: freshFixes });
13143
+ }
13144
+ return noFreshFixesPrompt;
13145
+ }
12868
13146
  };
12869
13147
  __publicField(_CheckForNewAvailableFixesService, "instance");
12870
13148
  var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
@@ -12873,7 +13151,7 @@ var CheckForNewAvailableFixesService = _CheckForNewAvailableFixesService;
12873
13151
  var CheckForNewAvailableFixesTool = class extends BaseTool {
12874
13152
  constructor() {
12875
13153
  super();
12876
- __publicField(this, "name", "check_for_new_available_fixes");
13154
+ __publicField(this, "name", MCP_TOOL_CHECK_FOR_NEW_AVAILABLE_FIXES);
12877
13155
  __publicField(this, "displayName", "Check for New Available Fixes");
12878
13156
  // A detailed description to guide the LLM on when and how to invoke this tool.
12879
13157
  __publicField(this, "description", `Continuesly monitors your code and scans for new security vulnerabilities.
@@ -12913,6 +13191,9 @@ Example payload:
12913
13191
  __publicField(this, "newFixesService");
12914
13192
  this.newFixesService = new CheckForNewAvailableFixesService();
12915
13193
  }
13194
+ triggerScan(args) {
13195
+ this.newFixesService.triggerScan(args);
13196
+ }
12916
13197
  async executeInternal(args) {
12917
13198
  const pathValidationResult = await validatePath(args.path);
12918
13199
  if (!pathValidationResult.isValid) {
@@ -12927,14 +13208,7 @@ Example payload:
12927
13208
  logInfo("CheckForNewAvailableFixesTool execution completed", {
12928
13209
  resultText
12929
13210
  });
12930
- return {
12931
- content: [
12932
- {
12933
- type: "text",
12934
- text: resultText
12935
- }
12936
- ]
12937
- };
13211
+ return this.createSuccessResponse(resultText);
12938
13212
  }
12939
13213
  };
12940
13214
 
@@ -12958,7 +13232,7 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
12958
13232
  }
12959
13233
  async initializeGqlClient() {
12960
13234
  if (!this.gqlClient) {
12961
- this.gqlClient = await getMcpGQLClient();
13235
+ this.gqlClient = await createAuthenticatedMcpGQLClient();
12962
13236
  }
12963
13237
  return this.gqlClient;
12964
13238
  }
@@ -12982,21 +13256,18 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
12982
13256
  logDebug("received latest report result", { fixReport, expiredReport });
12983
13257
  if (!fixReport) {
12984
13258
  if (expiredReport) {
13259
+ logInfo("Expired report found");
12985
13260
  const lastReportDate = expiredReport.expirationOn ? new Date(expiredReport.expirationOn).toLocaleString() : "Unknown date";
12986
- logInfo("Expired report found", {
13261
+ logDebug("Expired report ", {
12987
13262
  repoUrl,
12988
13263
  expirationOn: expiredReport.expirationOn
12989
13264
  });
12990
13265
  return expiredReportPrompt({ lastReportDate });
12991
13266
  }
12992
- logInfo("No report (active or expired) found for repository", {
12993
- repoUrl
12994
- });
13267
+ logInfo(`No report (active or expired) found for repository ${repoUrl}`);
12995
13268
  return noReportFoundPrompt;
12996
13269
  }
12997
- logInfo("Successfully retrieved available fixes", {
12998
- reportFound: true
12999
- });
13270
+ logInfo(`Successfully retrieved available fixes for ${repoUrl}`);
13000
13271
  const prompt = fixesFoundPrompt({
13001
13272
  fixReport,
13002
13273
  offset: effectiveOffset
@@ -13019,7 +13290,7 @@ var FetchAvailableFixesService = _FetchAvailableFixesService;
13019
13290
  var FetchAvailableFixesTool = class extends BaseTool {
13020
13291
  constructor() {
13021
13292
  super();
13022
- __publicField(this, "name", "fetch_available_fixes");
13293
+ __publicField(this, "name", MCP_TOOL_FETCH_AVAILABLE_FIXES);
13023
13294
  __publicField(this, "displayName", "Fetch Available Fixes");
13024
13295
  __publicField(this, "description", `Check the MOBB backend for pre-generated fixes (patch sets) that correspond to vulnerabilities detected in the supplied Git repository.
13025
13296
 
@@ -13039,7 +13310,7 @@ The tool will:
13039
13310
  2. Verify that the directory is a valid Git repository with an "origin" remote.
13040
13311
  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
13312
 
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.`);
13313
+ 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
13314
  __publicField(this, "inputSchema", {
13044
13315
  type: "object",
13045
13316
  properties: {
@@ -13095,17 +13366,10 @@ Call this tool instead of scan_and_fix_vulnerabilities when you only need a fixe
13095
13366
  limit: args.limit,
13096
13367
  offset: args.offset
13097
13368
  });
13098
- logInfo("FetchAvailableFixesTool execution completed successfully", {
13369
+ logDebug("FetchAvailableFixesTool execution completed successfully", {
13099
13370
  fixResult
13100
13371
  });
13101
- return {
13102
- content: [
13103
- {
13104
- type: "text",
13105
- text: fixResult
13106
- }
13107
- ]
13108
- };
13372
+ return this.createSuccessResponse(fixResult);
13109
13373
  }
13110
13374
  };
13111
13375
 
@@ -13157,9 +13421,10 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13157
13421
  limit,
13158
13422
  isRescan = false
13159
13423
  }) {
13424
+ logInfo("Processing vulnerabilities");
13160
13425
  try {
13161
13426
  this.gqlClient = await this.initializeGqlClient();
13162
- logInfo("storedFixReportId", {
13427
+ logDebug("storedFixReportId", {
13163
13428
  storedFixReportId: this.storedFixReportId,
13164
13429
  currentOffset: this.currentOffset,
13165
13430
  fixReportIdTimestamp: this.fixReportIdTimestamp,
@@ -13167,6 +13432,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13167
13432
  });
13168
13433
  let fixReportId = this.storedFixReportId;
13169
13434
  if (!fixReportId || isRescan || this.isFixReportIdExpired()) {
13435
+ logInfo("Scanning files");
13170
13436
  this.reset();
13171
13437
  this.validateFiles(fileList);
13172
13438
  const scanResult = await scanFiles(
@@ -13175,6 +13441,8 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13175
13441
  this.gqlClient
13176
13442
  );
13177
13443
  fixReportId = scanResult.fixReportId;
13444
+ } else {
13445
+ logInfo("Using stored fixReportId");
13178
13446
  }
13179
13447
  const effectiveOffset = offset ?? (this.currentOffset || 0);
13180
13448
  logDebug("effectiveOffset", { effectiveOffset });
@@ -13183,6 +13451,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13183
13451
  effectiveOffset,
13184
13452
  limit
13185
13453
  );
13454
+ logInfo(`Found ${fixes.totalCount} fixes`);
13186
13455
  if (fixes.totalCount > 0) {
13187
13456
  this.storedFixReportId = fixReportId;
13188
13457
  this.fixReportIdTimestamp = Date.now();
@@ -13209,13 +13478,14 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13209
13478
  }
13210
13479
  }
13211
13480
  async initializeGqlClient() {
13212
- const gqlClient = await getMcpGQLClient();
13213
- const isConnected = await gqlClient.verifyConnection();
13481
+ const gqlClient = await createAuthenticatedMcpGQLClient();
13482
+ const isConnected = await gqlClient.verifyApiConnection();
13214
13483
  if (!isConnected) {
13215
13484
  throw new ApiConnectionError(
13216
13485
  "Failed to connect to the API. Please check your MOBB_API_KEY"
13217
13486
  );
13218
13487
  }
13488
+ this.gqlClient = gqlClient;
13219
13489
  return gqlClient;
13220
13490
  }
13221
13491
  async getReportFixes(fixReportId, offset, limit) {
@@ -13228,7 +13498,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
13228
13498
  offset,
13229
13499
  limit
13230
13500
  });
13231
- logInfo("Fixes retrieved", { fixCount: fixes?.fixes?.length });
13501
+ logDebug(`${fixes?.fixes?.length} fixes retrieved`);
13232
13502
  return {
13233
13503
  fixes: fixes?.fixes || [],
13234
13504
  totalCount: fixes?.totalCount || 0
@@ -13242,7 +13512,7 @@ var ScanAndFixVulnerabilitiesService = _ScanAndFixVulnerabilitiesService;
13242
13512
  var ScanAndFixVulnerabilitiesTool = class extends BaseTool {
13243
13513
  constructor() {
13244
13514
  super();
13245
- __publicField(this, "name", "scan_and_fix_vulnerabilities");
13515
+ __publicField(this, "name", MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES);
13246
13516
  __publicField(this, "displayName", "Scan and Fix Vulnerabilities");
13247
13517
  // A detailed description to guide the LLM on when and how to invoke this tool.
13248
13518
  __publicField(this, "description", `Scans a given local repository for security vulnerabilities and returns auto-generated code fixes.
@@ -13322,7 +13592,9 @@ Example payload:
13322
13592
  this.vulnerabilityFixService.reset();
13323
13593
  }
13324
13594
  async executeInternal(args) {
13325
- logInfo("Executing tool: scan_and_fix_vulnerabilities", { path: args.path });
13595
+ logDebug(`Executing tool: ${MCP_TOOL_SCAN_AND_FIX_VULNERABILITIES}`, {
13596
+ path: args.path
13597
+ });
13326
13598
  if (!args.path) {
13327
13599
  throw new Error("Invalid arguments: Missing required parameter 'path'");
13328
13600
  }
@@ -13339,7 +13611,7 @@ Example payload:
13339
13611
  // 5MB
13340
13612
  maxFiles: args.maxFiles
13341
13613
  });
13342
- logInfo("Files", { files });
13614
+ logDebug("Files", { files });
13343
13615
  if (files.length === 0) {
13344
13616
  return {
13345
13617
  content: [
@@ -13358,34 +13630,20 @@ Example payload:
13358
13630
  limit: args.limit,
13359
13631
  isRescan: args.rescan || !!args.maxFiles
13360
13632
  });
13361
- const result = {
13362
- content: [
13363
- {
13364
- type: "text",
13365
- text: fixResult
13366
- }
13367
- ]
13368
- };
13369
- logInfo("Tool execution completed successfully", {
13633
+ const successResponse = this.createSuccessResponse(fixResult);
13634
+ logDebug("Tool execution completed successfully", {
13370
13635
  resultLength: fixResult.length,
13371
13636
  fileCount: files.length,
13372
- result
13637
+ result: successResponse
13373
13638
  });
13374
- return result;
13639
+ return successResponse;
13375
13640
  } catch (error) {
13376
- const errorResult = {
13377
- content: [
13378
- {
13379
- type: "text",
13380
- text: error.message
13381
- }
13382
- ]
13383
- };
13384
- logInfo("Tool execution failed", {
13641
+ const errorResponse = this.createSuccessResponse(error.message);
13642
+ logError("Tool execution failed", {
13385
13643
  error: error.message,
13386
- result: errorResult
13644
+ result: errorResponse
13387
13645
  });
13388
- return errorResult;
13646
+ return errorResponse;
13389
13647
  }
13390
13648
  }
13391
13649
  };