@hubspot/cli 8.0.5-experimental.0 → 8.0.7-experimental.0

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 (54) hide show
  1. package/commands/cms/__tests__/upload.test.js +0 -4
  2. package/commands/mcp/__tests__/start.test.js +8 -1
  3. package/commands/mcp/start.js +0 -1
  4. package/commands/project/__tests__/migrate.test.js +2 -2
  5. package/commands/project/migrate.js +4 -4
  6. package/lang/en.d.ts +4 -2
  7. package/lang/en.js +4 -2
  8. package/lib/mcp/__tests__/setup.test.js +16 -0
  9. package/lib/mcp/setup.d.ts +1 -0
  10. package/lib/mcp/setup.js +109 -34
  11. package/mcp-server/tools/cms/HsCreateFunctionTool.js +1 -1
  12. package/mcp-server/tools/cms/HsCreateModuleTool.js +1 -1
  13. package/mcp-server/tools/cms/HsCreateTemplateTool.js +1 -1
  14. package/mcp-server/tools/cms/HsFunctionLogsTool.js +1 -1
  15. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  16. package/mcp-server/tools/cms/HsListTool.js +1 -1
  17. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -2
  18. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +2 -2
  19. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +2 -2
  20. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +2 -2
  21. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +2 -2
  22. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +2 -2
  23. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +3 -20
  24. package/mcp-server/tools/project/AddFeatureToProjectTool.js +11 -7
  25. package/mcp-server/tools/project/CreateProjectTool.d.ts +4 -24
  26. package/mcp-server/tools/project/CreateProjectTool.js +11 -6
  27. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  28. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  29. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +8 -5
  30. package/mcp-server/tools/project/GetBuildLogsTool.d.ts +2 -2
  31. package/mcp-server/tools/project/GetBuildLogsTool.js +7 -6
  32. package/mcp-server/tools/project/GetBuildStatusTool.d.ts +1 -1
  33. package/mcp-server/tools/project/GetBuildStatusTool.js +4 -3
  34. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -6
  35. package/mcp-server/tools/project/GuidedWalkthroughTool.js +6 -1
  36. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  37. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  38. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +2 -2
  39. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +2 -2
  40. package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +2 -2
  41. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +2 -2
  42. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +32 -0
  43. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +10 -2
  44. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
  45. package/mcp-server/tools/project/constants.d.ts +1 -12
  46. package/mcp-server/tools/project/constants.js +16 -12
  47. package/mcp-server/utils/__tests__/command.test.js +233 -3
  48. package/mcp-server/utils/command.d.ts +5 -0
  49. package/mcp-server/utils/command.js +24 -0
  50. package/package.json +2 -2
  51. package/mcp-server/utils/__tests__/project.test.d.ts +0 -1
  52. package/mcp-server/utils/__tests__/project.test.js +0 -140
  53. package/mcp-server/utils/project.d.ts +0 -5
  54. package/mcp-server/utils/project.js +0 -18
@@ -8,7 +8,6 @@ import * as modulesLib from '@hubspot/local-dev-lib/cms/modules';
8
8
  import * as ignoreRulesLib from '@hubspot/local-dev-lib/ignoreRules';
9
9
  import * as themesLib from '@hubspot/local-dev-lib/cms/themes';
10
10
  import * as configLib from '@hubspot/local-dev-lib/config';
11
- import * as handleFieldsJSLib from '@hubspot/local-dev-lib/cms/handleFieldsJS';
12
11
  import { uiLogger } from '../../../lib/ui/logger.js';
13
12
  import * as errorHandlers from '../../../lib/errorHandlers/index.js';
14
13
  import * as commonOpts from '../../../lib/commonOpts.js';
@@ -27,7 +26,6 @@ vi.mock('@hubspot/local-dev-lib/cms/modules');
27
26
  vi.mock('@hubspot/local-dev-lib/ignoreRules');
28
27
  vi.mock('@hubspot/local-dev-lib/cms/themes');
29
28
  vi.mock('@hubspot/local-dev-lib/config');
30
- vi.mock('@hubspot/local-dev-lib/cms/handleFieldsJS');
31
29
  vi.mock('../../../lib/errorHandlers/index.js');
32
30
  vi.mock('../../../lib/commonOpts.js');
33
31
  vi.mock('../../../lib/prompts/uploadPrompt.js');
@@ -55,7 +53,6 @@ const hasUploadErrorsSpy = vi.spyOn(uploadFolderLib, 'hasUploadErrors');
55
53
  const processExitSpy = vi.spyOn(process, 'exit');
56
54
  const logErrorSpy = vi.spyOn(errorHandlers, 'logError');
57
55
  const getConfigAccountIfExistsSpy = vi.spyOn(configLib, 'getConfigAccountIfExists');
58
- const isConvertableFieldJsSpy = vi.spyOn(handleFieldsJSLib, 'isConvertableFieldJs');
59
56
  describe('commands/cms/upload', () => {
60
57
  beforeEach(() => {
61
58
  // @ts-expect-error Mock implementation
@@ -70,7 +67,6 @@ describe('commands/cms/upload', () => {
70
67
  getThemePreviewUrlSpy.mockReturnValue(undefined);
71
68
  // Mock config to prevent reading actual config file in CI
72
69
  getConfigAccountIfExistsSpy.mockReturnValue(undefined);
73
- isConvertableFieldJsSpy.mockReturnValue(false);
74
70
  });
75
71
  describe('command', () => {
76
72
  it('should have the correct command structure', () => {
@@ -7,14 +7,20 @@ import * as errorHandlers from '../../../lib/errorHandlers/index.js';
7
7
  import * as usageTrackingLib from '../../../lib/usageTracking.js';
8
8
  import * as processLib from '../../../lib/process.js';
9
9
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
10
- import startCommand from '../start.js';
10
+ // Create a mock execAsync function before importing the module
11
+ const execAsyncMock = vi.fn();
11
12
  vi.mock('yargs');
12
13
  vi.mock('../../../lib/commonOpts');
13
14
  vi.mock('node:child_process');
15
+ vi.mock('node:util', () => ({
16
+ promisify: vi.fn(() => execAsyncMock),
17
+ }));
14
18
  vi.mock('fs');
15
19
  vi.mock('@hubspot/local-dev-lib/config');
16
20
  vi.mock('../../../lib/errorHandlers/index.js');
17
21
  vi.mock('../../../lib/process.js');
22
+ // Import after mocks are set up
23
+ const startCommand = await import('../start.js').then(m => m.default);
18
24
  const spawnSpy = vi.mocked(spawn);
19
25
  const existsSyncSpy = vi.spyOn(fs, 'existsSync');
20
26
  const trackCommandUsageSpy = vi.spyOn(usageTrackingLib, 'trackCommandUsage');
@@ -36,6 +42,7 @@ describe('commands/mcp/start', () => {
36
42
  processExitSpy.mockImplementation(() => { });
37
43
  // Mock config to prevent reading actual config file in CI
38
44
  getConfigAccountIfExistsSpy.mockReturnValue(undefined);
45
+ execAsyncMock.mockClear();
39
46
  });
40
47
  describe('command', () => {
41
48
  it('should have the correct command structure', () => {
@@ -28,7 +28,6 @@ async function startMcpServer(aiAgent) {
28
28
  uiLogger.debug(commands.mcp.start.startingServer);
29
29
  uiLogger.debug(commands.mcp.start.stopInstructions);
30
30
  const args = [serverPath];
31
- // Start the server using ts-node
32
31
  const child = spawn(`node`, args, {
33
32
  stdio: 'inherit',
34
33
  env: {
@@ -9,7 +9,7 @@ import { uiBetaTag, uiCommandReference } from '../../../lib/ui/index.js';
9
9
  vi.mock('../../../lib/app/migrate');
10
10
  vi.mock('../../../lib/projects/config');
11
11
  vi.mock('../../../lib/ui');
12
- const { v2025_2, v2026_3_beta } = PLATFORM_VERSIONS;
12
+ const { v2025_2 } = PLATFORM_VERSIONS;
13
13
  describe('commands/project/migrate', () => {
14
14
  const yargsMock = yargs;
15
15
  const optionsSpy = vi.spyOn(yargsMock, 'option').mockReturnValue(yargsMock);
@@ -48,7 +48,7 @@ describe('commands/project/migrate', () => {
48
48
  migrateCommand.builder(yargsMock);
49
49
  expect(optionsSpy).toHaveBeenCalledWith('platform-version', {
50
50
  type: 'string',
51
- choices: [v2025_2, v2026_3_beta],
51
+ choices: [v2025_2],
52
52
  default: v2025_2,
53
53
  });
54
54
  expect(optionsSpy).toHaveBeenCalledWith('unstable', {
@@ -13,7 +13,7 @@ import { getHasMigratableThemes, migrateThemes2025_2, } from '../../lib/theme/mi
13
13
  import { hasFeature } from '../../lib/hasFeature.js';
14
14
  import { FEATURES } from '../../lib/constants.js';
15
15
  import { trackCommandMetadataUsage, trackCommandUsage, } from '../../lib/usageTracking.js';
16
- const { v2025_2, v2026_3_beta } = PLATFORM_VERSIONS;
16
+ const { v2025_2 } = PLATFORM_VERSIONS;
17
17
  const command = 'migrate';
18
18
  const describe = commands.project.migrate.describe;
19
19
  async function handler(args) {
@@ -26,8 +26,8 @@ async function handler(args) {
26
26
  }
27
27
  if (projectConfig?.projectConfig) {
28
28
  await renderInline(getWarningBox({
29
- title: lib.migrate.projectMigrationWarningTitle(platformVersion),
30
- message: lib.migrate.projectMigrationWarning(platformVersion),
29
+ title: lib.migrate.projectMigrationWarningTitle,
30
+ message: lib.migrate.projectMigrationWarning,
31
31
  }));
32
32
  }
33
33
  try {
@@ -67,7 +67,7 @@ function projectMigrateBuilder(yargs) {
67
67
  yargs
68
68
  .option('platform-version', {
69
69
  type: 'string',
70
- choices: [v2025_2, v2026_3_beta],
70
+ choices: [v2025_2],
71
71
  default: v2025_2,
72
72
  })
73
73
  .option('unstable', {
package/lang/en.d.ts CHANGED
@@ -1295,6 +1295,8 @@ export declare const commands: {
1295
1295
  prompts: {
1296
1296
  targets: string;
1297
1297
  targetsRequired: string;
1298
+ standaloneMode: string;
1299
+ cliVersion: string;
1298
1300
  };
1299
1301
  };
1300
1302
  start: {
@@ -3923,8 +3925,8 @@ export declare const lib: {
3923
3925
  componentsToBeMigrated: (components: string) => string;
3924
3926
  componentsThatWillNotBeMigrated: (components: string) => string;
3925
3927
  sourceContentsMoved: (newLocation: string) => string;
3926
- projectMigrationWarningTitle: (platformVersion: string) => string;
3927
- projectMigrationWarning: (platformVersion: string) => string;
3928
+ projectMigrationWarningTitle: string;
3929
+ projectMigrationWarning: string;
3928
3930
  exitWithoutMigrating: string;
3929
3931
  success: {
3930
3932
  downloadedProject: (projectName: string, projectDest: string) => string;
package/lang/en.js CHANGED
@@ -1311,6 +1311,8 @@ export const commands = {
1311
1311
  prompts: {
1312
1312
  targets: '[--client] Which tools would you like to add the HubSpot CLI MCP server to?',
1313
1313
  targetsRequired: 'Must choose at least one app to configure.',
1314
+ standaloneMode: 'Do you want to run in standalone mode? (This will use npx @hubspot/cli instead of the installed hs command)',
1315
+ cliVersion: 'Specify a CLI version to pin (leave blank for latest):',
1314
1316
  },
1315
1317
  },
1316
1318
  start: {
@@ -3946,8 +3948,8 @@ export const lib = {
3946
3948
  componentsToBeMigrated: (components) => `The following features will be migrated: ${components}`,
3947
3949
  componentsThatWillNotBeMigrated: (components) => `[NOTE] These features are not yet supported for migration but will be available later: ${components}`,
3948
3950
  sourceContentsMoved: (newLocation) => `The contents of your old source directory have been moved to ${newLocation}, move any required files to the new source directory.`,
3949
- projectMigrationWarningTitle: (platformVersion) => `Important: Migrating to platformVersion ${platformVersion} is irreversible`,
3950
- projectMigrationWarning: (platformVersion) => uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion ${platformVersion}. This action cannot be undone. To ensure you have access to your original files, they will be copied to a new directory (archive) for safekeeping.\n\nThis command will guide you through the process, prompting you to enter the required fields and will download the new project source code into your project source directory.`, false),
3951
+ projectMigrationWarningTitle: 'Important: Migrating to platformVersion 2025.2 is irreversible',
3952
+ projectMigrationWarning: uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion 2025.2. This action cannot be undone. To ensure you have access to your original files, they will be copied to a new directory (archive) for safekeeping.\n\nThis command will guide you through the process, prompting you to enter the required fields and will download the new project source code into your project source directory.`, false),
3951
3953
  exitWithoutMigrating: 'Exiting without migrating',
3952
3954
  success: {
3953
3955
  downloadedProject: (projectName, projectDest) => `Saved ${projectName} to ${projectDest}`,
@@ -132,6 +132,22 @@ describe('lib/mcp/setup', () => {
132
132
  });
133
133
  expect(mockedLogError).toHaveBeenCalledWith(error);
134
134
  });
135
+ it('should pass through environment variables in command', async () => {
136
+ const mockMcpCommandWithEnv = {
137
+ command: 'test-command',
138
+ args: ['--arg1'],
139
+ env: { HUBSPOT_MCP_STANDALONE: 'true' },
140
+ };
141
+ mockedExecAsync.mockResolvedValueOnce({
142
+ stdout: 'codex version 1.0.0',
143
+ stderr: '',
144
+ });
145
+ mockedExecAsync.mockResolvedValueOnce({ stdout: '', stderr: '' });
146
+ const result = await setupCodex(mockMcpCommandWithEnv);
147
+ expect(result).toBe(true);
148
+ // The HUBSPOT_MCP_STANDALONE env var is now passed in the command string using 'env'
149
+ expect(mockedExecAsync).toHaveBeenCalledWith('codex mcp add "HubSpotDev" -- env HUBSPOT_MCP_STANDALONE=true test-command --arg1 --ai-agent codex');
150
+ });
135
151
  });
136
152
  describe('setupGemini', () => {
137
153
  const mockMcpCommand = {
@@ -5,6 +5,7 @@ export declare const supportedTools: {
5
5
  interface McpCommand {
6
6
  command: string;
7
7
  args: string[];
8
+ env?: Record<string, string>;
8
9
  }
9
10
  export declare function addMcpServerToConfig(targets: string[] | undefined): Promise<string[]>;
10
11
  export declare function setupVsCode(mcpCommand?: McpCommand): Promise<boolean>;
package/lib/mcp/setup.js CHANGED
@@ -47,23 +47,56 @@ export async function addMcpServerToConfig(targets) {
47
47
  else {
48
48
  derivedTargets = targets;
49
49
  }
50
+ // Prompt for standalone mode
51
+ const { useStandaloneMode } = await promptUser({
52
+ name: 'useStandaloneMode',
53
+ type: 'confirm',
54
+ message: commands.mcp.setup.prompts.standaloneMode,
55
+ default: false,
56
+ });
57
+ const { cliVersion } = useStandaloneMode
58
+ ? await promptUser({
59
+ name: 'cliVersion',
60
+ type: 'input',
61
+ message: commands.mcp.setup.prompts.cliVersion,
62
+ validate: (v) => !v || /^[\d]+\.[\d]+\.[\d]+([-+][\w.]+)?$/.test(v.trim())
63
+ ? true
64
+ : 'Please enter a valid semver version (e.g. 8.0.1) or leave blank for latest.',
65
+ })
66
+ : { cliVersion: '' };
67
+ const cliPackage = cliVersion
68
+ ? `@hubspot/cli@${cliVersion}`
69
+ : '@hubspot/cli';
70
+ const standaloneEnv = {
71
+ HUBSPOT_MCP_STANDALONE: 'true',
72
+ };
73
+ if (cliVersion) {
74
+ standaloneEnv.HUBSPOT_CLI_VERSION = cliVersion;
75
+ }
76
+ const mcpCommand = useStandaloneMode
77
+ ? {
78
+ command: 'npx',
79
+ args: ['-y', '-p', cliPackage, 'hs', 'mcp', 'start'],
80
+ env: standaloneEnv,
81
+ }
82
+ : defaultMcpCommand;
50
83
  if (derivedTargets.includes(claudeCode)) {
51
- await runSetupFunction(setupClaudeCode);
84
+ await runSetupFunction(() => setupClaudeCode(mcpCommand));
52
85
  }
53
86
  if (derivedTargets.includes(cursor)) {
54
- await runSetupFunction(setupCursor);
87
+ await runSetupFunction(() => setupCursor(mcpCommand));
55
88
  }
56
89
  if (derivedTargets.includes(windsurf)) {
57
- await runSetupFunction(setupWindsurf);
90
+ await runSetupFunction(() => setupWindsurf(mcpCommand));
58
91
  }
59
92
  if (derivedTargets.includes(vscode)) {
60
- await runSetupFunction(setupVsCode);
93
+ await runSetupFunction(() => setupVsCode(mcpCommand));
61
94
  }
62
95
  if (derivedTargets.includes(codex)) {
63
- await runSetupFunction(setupCodex);
96
+ await runSetupFunction(() => setupCodex(mcpCommand));
64
97
  }
65
98
  if (derivedTargets.includes(gemini)) {
66
- await runSetupFunction(setupGemini);
99
+ await runSetupFunction(() => setupGemini(mcpCommand));
67
100
  }
68
101
  uiLogger.info(commands.mcp.setup.success(derivedTargets));
69
102
  return derivedTargets;
@@ -122,9 +155,14 @@ function setupMcpConfigFile(config) {
122
155
  mcpConfig.mcpServers = {};
123
156
  }
124
157
  // Add or update HubSpot CLI MCP server
125
- mcpConfig.mcpServers[mcpServerName] = {
126
- ...config.mcpCommand,
158
+ const serverConfig = {
159
+ command: config.mcpCommand.command,
160
+ args: config.mcpCommand.args,
127
161
  };
162
+ if (config.mcpCommand.env) {
163
+ serverConfig.env = config.mcpCommand.env;
164
+ }
165
+ mcpConfig.mcpServers[mcpServerName] = serverConfig;
128
166
  // Write the updated config
129
167
  fs.writeFileSync(config.configPath, JSON.stringify(mcpConfig, null, 2));
130
168
  SpinniesManager.succeed('spinner', {
@@ -145,10 +183,16 @@ export async function setupVsCode(mcpCommand = defaultMcpCommand) {
145
183
  SpinniesManager.add('vsCode', {
146
184
  text: commands.mcp.setup.spinners.configuringVsCode,
147
185
  });
148
- const mcpConfig = JSON.stringify({
186
+ const commandWithAgent = buildCommandWithAgentString(mcpCommand, vscode);
187
+ const configObject = {
149
188
  name: mcpServerName,
150
- ...buildCommandWithAgentString(mcpCommand, vscode),
151
- });
189
+ command: commandWithAgent.command,
190
+ args: commandWithAgent.args,
191
+ };
192
+ if (commandWithAgent.env) {
193
+ configObject.env = commandWithAgent.env;
194
+ }
195
+ const mcpConfig = JSON.stringify(configObject);
152
196
  await execAsync(`code --add-mcp ${JSON.stringify(mcpConfig)}`);
153
197
  SpinniesManager.succeed('vsCode', {
154
198
  text: commands.mcp.setup.spinners.configuredVsCode,
@@ -179,30 +223,44 @@ export async function setupClaudeCode(mcpCommand = defaultMcpCommand) {
179
223
  try {
180
224
  // Check if claude command is available
181
225
  await execAsync('claude --version');
182
- }
183
- catch (e) {
184
- SpinniesManager.fail('claudeCode', {
185
- text: commands.mcp.setup.spinners.claudeCodeNotFound,
226
+ // Run claude mcp add command
227
+ const commandWithAgent = buildCommandWithAgentString(mcpCommand, claudeCode);
228
+ const configObject = {
229
+ type: 'stdio',
230
+ command: commandWithAgent.command,
231
+ args: commandWithAgent.args,
232
+ };
233
+ if (commandWithAgent.env) {
234
+ configObject.env = commandWithAgent.env;
235
+ }
236
+ const mcpConfig = JSON.stringify(configObject);
237
+ const { stdout } = await execAsync('claude mcp list');
238
+ if (stdout.includes(mcpServerName)) {
239
+ SpinniesManager.update('claudeCode', {
240
+ text: commands.mcp.setup.spinners.alreadyInstalled,
241
+ });
242
+ await execAsync(`claude mcp remove "${mcpServerName}" --scope user`);
243
+ }
244
+ await execAsync(`claude mcp add-json "${mcpServerName}" ${JSON.stringify(mcpConfig)} --scope user`);
245
+ SpinniesManager.succeed('claudeCode', {
246
+ text: commands.mcp.setup.spinners.configuredClaudeCode,
186
247
  });
187
- return false;
248
+ return true;
188
249
  }
189
- // Run claude mcp add command
190
- const mcpConfig = JSON.stringify({
191
- type: 'stdio',
192
- ...buildCommandWithAgentString(mcpCommand, claudeCode),
193
- });
194
- const { stdout } = await execAsync('claude mcp list');
195
- if (stdout.includes(mcpServerName)) {
196
- SpinniesManager.update('claudeCode', {
197
- text: commands.mcp.setup.spinners.alreadyInstalled,
198
- });
199
- await execAsync(`claude mcp remove "${mcpServerName}" --scope user`);
250
+ catch (error) {
251
+ if (error instanceof Error && error.message.includes('claude')) {
252
+ SpinniesManager.fail('claudeCode', {
253
+ text: commands.mcp.setup.spinners.claudeCodeNotFound,
254
+ });
255
+ }
256
+ else {
257
+ SpinniesManager.fail('claudeCode', {
258
+ text: commands.mcp.setup.spinners.claudeCodeInstallFailed,
259
+ });
260
+ logError(error);
261
+ }
262
+ return false;
200
263
  }
201
- await execAsync(`claude mcp add-json "${mcpServerName}" ${JSON.stringify(mcpConfig)} --scope user`);
202
- SpinniesManager.succeed('claudeCode', {
203
- text: commands.mcp.setup.spinners.configuredClaudeCode,
204
- });
205
- return true;
206
264
  }
207
265
  catch (error) {
208
266
  SpinniesManager.fail('claudeCode', {
@@ -248,7 +306,8 @@ export async function setupCodex(mcpCommand = defaultMcpCommand) {
248
306
  return false;
249
307
  }
250
308
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, codex);
251
- await execAsync(`codex mcp add "${mcpServerName}" -- ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
309
+ const commandString = buildCommandStringWithEnv(mcpCommandWithAgent);
310
+ await execAsync(`codex mcp add "${mcpServerName}" -- ${commandString}`);
252
311
  SpinniesManager.succeed('codexSpinner', {
253
312
  text: commands.mcp.setup.spinners.configuredCodex,
254
313
  });
@@ -277,7 +336,8 @@ export async function setupGemini(mcpCommand = defaultMcpCommand) {
277
336
  return false;
278
337
  }
279
338
  const mcpCommandWithAgent = buildCommandWithAgentString(mcpCommand, gemini);
280
- await execAsync(`gemini mcp add -s user "${mcpServerName}" ${mcpCommandWithAgent.command} ${mcpCommandWithAgent.args.join(' ')}`);
339
+ const commandString = buildCommandStringWithEnv(mcpCommandWithAgent);
340
+ await execAsync(`gemini mcp add -s user "${mcpServerName}" ${commandString}`);
281
341
  SpinniesManager.succeed('geminiSpinner', {
282
342
  text: commands.mcp.setup.spinners.configuredGemini,
283
343
  });
@@ -296,3 +356,18 @@ function buildCommandWithAgentString(mcpCommand, agent) {
296
356
  mcpCommandCopy.args.push('--ai-agent', agent);
297
357
  return mcpCommandCopy;
298
358
  }
359
+ function buildCommandStringWithEnv(mcpCommand) {
360
+ const parts = [];
361
+ // Only pass HUBSPOT_MCP_STANDALONE environment variable if it exists
362
+ if (mcpCommand.env?.HUBSPOT_MCP_STANDALONE) {
363
+ parts.push(`HUBSPOT_MCP_STANDALONE=${mcpCommand.env.HUBSPOT_MCP_STANDALONE};`);
364
+ }
365
+ // Only pass HUBSPOT_MCP_STANDALONE environment variable if it exists
366
+ if (mcpCommand.env?.HUBSPOT_CLI_VERSION) {
367
+ parts.push(`HUBSPOT_CLI_VERSION=${mcpCommand.env.HUBSPOT_CLI_VERSION};`);
368
+ }
369
+ // Add the command and args
370
+ parts.push(mcpCommand.command);
371
+ parts.push(...mcpCommand.args);
372
+ return parts.join(' ');
373
+ }
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
- import { runCommandInDir } from '../../utils/project.js';
4
+ import { runCommandInDir } from '../../utils/command.js';
5
5
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  import { addFlag } from '../../utils/command.js';
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
- import { runCommandInDir } from '../../utils/project.js';
4
+ import { runCommandInDir } from '../../utils/command.js';
5
5
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  import { addFlag } from '../../utils/command.js';
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
- import { runCommandInDir } from '../../utils/project.js';
4
+ import { runCommandInDir } from '../../utils/command.js';
5
5
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  import { addFlag } from '../../utils/command.js';
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { addFlag } from '../../utils/command.js';
4
4
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
5
- import { runCommandInDir } from '../../utils/project.js';
5
+ import { runCommandInDir } from '../../utils/command.js';
6
6
  import { formatTextContents } from '../../utils/content.js';
7
7
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
8
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { addFlag } from '../../utils/command.js';
4
4
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
5
- import { runCommandInDir } from '../../utils/project.js';
5
+ import { runCommandInDir } from '../../utils/command.js';
6
6
  import { formatTextContents } from '../../utils/content.js';
7
7
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
8
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -2,7 +2,7 @@ import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { addFlag } from '../../utils/command.js';
4
4
  import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
5
- import { runCommandInDir } from '../../utils/project.js';
5
+ import { runCommandInDir } from '../../utils/command.js';
6
6
  import { formatTextContents } from '../../utils/content.js';
7
7
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
8
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -1,11 +1,10 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateFunctionTool } from '../HsCreateFunctionTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
9
8
  vi.mock('../../../utils/command');
10
9
  vi.mock('../../../utils/toolUsageTracking');
11
10
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateModuleTool } from '../HsCreateModuleTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsCreateTemplateTool } from '../HsCreateTemplateTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsFunctionLogsTool } from '../HsFunctionLogsTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsListFunctionsTool } from '../HsListFunctionsTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { HsListTool } from '../HsListTool.js';
3
- import { runCommandInDir } from '../../../utils/project.js';
3
+ import { runCommandInDir } from '../../../utils/command.js';
4
4
  import { addFlag } from '../../../utils/command.js';
5
5
  import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
6
  import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
7
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
8
- vi.mock('../../../utils/project');
8
+ vi.mock('../../../utils/command');
9
9
  vi.mock('../../../utils/command');
10
10
  vi.mock('../../../utils/toolUsageTracking');
11
11
  vi.mock('../../../utils/feedbackTracking');
@@ -5,26 +5,9 @@ declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteProjectPath: z.ZodString;
6
6
  absoluteCurrentWorkingDirectory: z.ZodString;
7
7
  addApp: z.ZodBoolean;
8
- distribution: z.ZodOptional<z.ZodEnum<{
9
- marketplace: "marketplace";
10
- private: "private";
11
- }>>;
12
- auth: z.ZodOptional<z.ZodEnum<{
13
- oauth: "oauth";
14
- static: "static";
15
- }>>;
16
- features: z.ZodOptional<z.ZodArray<z.ZodEnum<{
17
- card: "card";
18
- settings: "settings";
19
- "app-event": "app-event";
20
- page: "page";
21
- "workflow-action-tool": "workflow-action-tool";
22
- webhooks: "webhooks";
23
- "workflow-action": "workflow-action";
24
- "app-function": "app-function";
25
- "app-object": "app-object";
26
- scim: "scim";
27
- }>>>;
8
+ distribution: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"marketplace">, z.ZodLiteral<"private">]>>;
9
+ auth: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"static">, z.ZodLiteral<"oauth">]>>;
10
+ features: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">, z.ZodLiteral<"workflow-action-tool">, z.ZodLiteral<"app-object">, z.ZodLiteral<"app-event">, z.ZodLiteral<"scim">, z.ZodLiteral<"page">]>>>;
28
11
  }, z.core.$strip>;
29
12
  export type AddFeatureInputSchema = z.infer<typeof inputSchemaZodObject>;
30
13
  export declare class AddFeatureToProjectTool extends Tool<AddFeatureInputSchema> {
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../lib/constants.js';
4
4
  import { addFlag } from '../../utils/command.js';
5
5
  import { absoluteCurrentWorkingDirectory, absoluteProjectPath, features, } from './constants.js';
6
- import { runCommandInDir } from '../../utils/project.js';
6
+ import { runCommandInDir } from '../../utils/command.js';
7
7
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
8
8
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
9
9
  import { setupHubSpotConfig } from '../../utils/config.js';
@@ -15,13 +15,17 @@ const inputSchema = {
15
15
  .boolean()
16
16
  .describe('Should an app be added? If there is no app in the project, an app must be added to add a feature'),
17
17
  distribution: z
18
- .enum([APP_DISTRIBUTION_TYPES.MARKETPLACE, APP_DISTRIBUTION_TYPES.PRIVATE])
19
- .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. ')
20
- .optional(),
18
+ .optional(z.union([
19
+ z.literal(APP_DISTRIBUTION_TYPES.MARKETPLACE),
20
+ z.literal(APP_DISTRIBUTION_TYPES.PRIVATE),
21
+ ]))
22
+ .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Private is used if you do not wish to distribute your app on the HubSpot marketplace. '),
21
23
  auth: z
22
- .enum([APP_AUTH_TYPES.STATIC, APP_AUTH_TYPES.OAUTH])
23
- .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. ')
24
- .optional(),
24
+ .optional(z.union([
25
+ z.literal(APP_AUTH_TYPES.STATIC),
26
+ z.literal(APP_AUTH_TYPES.OAUTH),
27
+ ]))
28
+ .describe('If not specified by the user, DO NOT choose for them. This cannot be changed after a project is uploaded. Static uses a static non changing authentication token, and is only available for private distribution. '),
25
29
  features,
26
30
  };
27
31
  // eslint-disable-next-line @typescript-eslint/no-unused-vars