@hubspot/cli 7.7.22-experimental.0 → 7.7.24-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 (82) hide show
  1. package/commands/account/auth.js +15 -4
  2. package/commands/auth.js +1 -1
  3. package/commands/config/set.d.ts +1 -0
  4. package/commands/config/set.js +19 -9
  5. package/commands/mcp/start.d.ts +1 -0
  6. package/commands/mcp/start.js +12 -4
  7. package/commands/project/create.js +2 -2
  8. package/commands/project/validate.js +1 -0
  9. package/commands/sandbox/__tests__/create.test.js +207 -0
  10. package/commands/sandbox/create.d.ts +1 -1
  11. package/commands/sandbox/create.js +31 -16
  12. package/commands/testAccount/createConfig.js +1 -1
  13. package/lang/en.d.ts +17 -4
  14. package/lang/en.js +22 -6
  15. package/lang/en.lyaml +4 -2
  16. package/lib/__tests__/buildAccount.test.js +62 -4
  17. package/lib/__tests__/yargsUtils.test.js +12 -1
  18. package/lib/buildAccount.d.ts +4 -1
  19. package/lib/buildAccount.js +57 -2
  20. package/lib/commonOpts.js +25 -0
  21. package/lib/configOptions.d.ts +5 -0
  22. package/lib/configOptions.js +11 -1
  23. package/lib/constants.d.ts +8 -0
  24. package/lib/constants.js +8 -0
  25. package/lib/errorHandlers/index.js +1 -3
  26. package/lib/errors/ProjectValidationError.d.ts +4 -0
  27. package/lib/errors/ProjectValidationError.js +9 -0
  28. package/lib/mcp/setup.d.ts +4 -0
  29. package/lib/mcp/setup.js +36 -0
  30. package/lib/projects/__tests__/LocalDevProcess.test.js +35 -0
  31. package/lib/projects/__tests__/LocalDevWebsocketServer.test.js +170 -1
  32. package/lib/projects/add/legacyAddComponent.js +1 -1
  33. package/lib/projects/add/v3AddComponent.js +3 -2
  34. package/lib/projects/create/index.js +3 -3
  35. package/lib/projects/create/legacy.js +2 -2
  36. package/lib/projects/create/v3.d.ts +0 -2
  37. package/lib/projects/create/v3.js +1 -3
  38. package/lib/projects/localDev/LocalDevLogger.js +11 -2
  39. package/lib/projects/localDev/LocalDevProcess.d.ts +2 -0
  40. package/lib/projects/localDev/LocalDevProcess.js +15 -0
  41. package/lib/projects/localDev/LocalDevState.d.ts +1 -0
  42. package/lib/projects/localDev/LocalDevState.js +5 -0
  43. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +2 -2
  44. package/lib/projects/localDev/LocalDevWebsocketServer.js +40 -30
  45. package/lib/projects/upload.js +5 -12
  46. package/lib/projects/urls.d.ts +1 -1
  47. package/lib/projects/urls.js +2 -2
  48. package/lib/sandboxes.d.ts +4 -0
  49. package/lib/sandboxes.js +4 -0
  50. package/lib/ui/index.d.ts +6 -0
  51. package/lib/ui/index.js +3 -5
  52. package/lib/yargsUtils.d.ts +1 -0
  53. package/lib/yargsUtils.js +6 -0
  54. package/mcp-server/tools/index.js +6 -4
  55. package/mcp-server/tools/project/{AddFeatureToProject.d.ts → AddFeatureToProjectTool.d.ts} +4 -4
  56. package/mcp-server/tools/project/{AddFeatureToProject.js → AddFeatureToProjectTool.js} +6 -14
  57. package/mcp-server/tools/project/CreateProjectTool.d.ts +3 -3
  58. package/mcp-server/tools/project/CreateProjectTool.js +4 -14
  59. package/mcp-server/tools/project/{DeployProject.d.ts → DeployProjectTool.d.ts} +1 -1
  60. package/mcp-server/tools/project/{DeployProject.js → DeployProjectTool.js} +2 -2
  61. package/mcp-server/tools/project/GetConfigValuesTool.d.ts +20 -0
  62. package/mcp-server/tools/project/GetConfigValuesTool.js +51 -0
  63. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  64. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  65. package/mcp-server/tools/project/__tests__/{AddFeatureToProject.test.js → AddFeatureToProjectTool.test.js} +7 -7
  66. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +3 -4
  67. package/mcp-server/tools/project/__tests__/{DeployProject.test.js → DeployProjectTool.test.js} +4 -4
  68. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +198 -0
  69. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +2 -2
  70. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +2 -2
  71. package/mcp-server/tools/project/constants.d.ts +1 -0
  72. package/mcp-server/tools/project/constants.js +11 -0
  73. package/mcp-server/utils/__tests__/command.test.js +76 -3
  74. package/mcp-server/utils/command.d.ts +6 -0
  75. package/mcp-server/utils/command.js +19 -0
  76. package/package.json +3 -3
  77. package/mcp-server/utils/__tests__/project.test.js +0 -79
  78. package/mcp-server/utils/project.d.ts +0 -5
  79. package/mcp-server/utils/project.js +0 -14
  80. /package/mcp-server/tools/project/__tests__/{AddFeatureToProject.test.d.ts → AddFeatureToProjectTool.test.d.ts} +0 -0
  81. /package/mcp-server/tools/project/__tests__/{DeployProject.test.d.ts → DeployProjectTool.test.d.ts} +0 -0
  82. /package/mcp-server/{utils/__tests__/project.test.d.ts → tools/project/__tests__/GetConfigValuesTool.test.d.ts} +0 -0
package/lib/ui/index.d.ts CHANGED
@@ -1,9 +1,14 @@
1
+ type TerminalSupport = {
2
+ hyperlinks: boolean;
3
+ color: boolean;
4
+ };
1
5
  export declare const UI_COLORS: {
2
6
  SORBET: string;
3
7
  MARIGOLD: string;
4
8
  MARIGOLD_DARK: string;
5
9
  };
6
10
  export declare function uiLine(): void;
11
+ export declare function getTerminalUISupport(): TerminalSupport;
7
12
  export declare function uiLink(linkText: string, url: string): string;
8
13
  export declare function uiAccountDescription(accountId?: number | null, bold?: boolean): string;
9
14
  export declare function uiInfoSection(title: string, logContent: () => void): void;
@@ -17,3 +22,4 @@ export declare function uiCommandDisabledBanner(command: string, url?: string, m
17
22
  export declare function uiDeprecatedDescription(message: string, command: string, url?: string): undefined;
18
23
  export declare function uiDeprecatedMessage(command: string, url?: string, message?: string): void;
19
24
  export declare function indent(level: number): string;
25
+ export {};
package/lib/ui/index.js CHANGED
@@ -13,7 +13,7 @@ export const UI_COLORS = {
13
13
  export function uiLine() {
14
14
  logger.log('-'.repeat(50));
15
15
  }
16
- function getTerminalUISupport() {
16
+ export function getTerminalUISupport() {
17
17
  return {
18
18
  hyperlinks: supportsHyperlinkModule.stdout,
19
19
  color: supportsColor.stdout.hasBasic,
@@ -79,9 +79,8 @@ export function uiFeatureHighlight(features, title) {
79
79
  });
80
80
  }
81
81
  export function uiBetaTag(message, log = true) {
82
- const terminalUISupport = getTerminalUISupport();
83
82
  const tag = i18n(`lib.ui.betaTag`);
84
- const result = `${terminalUISupport.color ? chalk.hex(UI_COLORS.SORBET)(tag) : tag} ${message}`;
83
+ const result = `${tag} ${message}`;
85
84
  if (log) {
86
85
  logger.log(result);
87
86
  return;
@@ -89,9 +88,8 @@ export function uiBetaTag(message, log = true) {
89
88
  return result;
90
89
  }
91
90
  export function uiDeprecatedTag(message, log = true) {
92
- const terminalUISupport = getTerminalUISupport();
93
91
  const tag = i18n(`lib.ui.deprecatedTag`);
94
- const result = `${terminalUISupport.color ? chalk.yellow(tag) : tag} ${message}`;
92
+ const result = `${tag} ${message}`;
95
93
  if (log) {
96
94
  logger.log(result);
97
95
  return;
@@ -13,3 +13,4 @@ export declare function makeYargsBuilder<T>(callback: (yargs: Argv) => Argv<T>,
13
13
  };
14
14
  useJSONOutputOptions?: boolean;
15
15
  }): (yargs: Argv) => Promise<Argv<T>>;
16
+ export declare function getExclusiveConflicts(options: string[]): Record<string, string[]>;
package/lib/yargsUtils.js CHANGED
@@ -34,3 +34,9 @@ export function makeYargsBuilder(callback, command, describe, options = {}) {
34
34
  return result;
35
35
  };
36
36
  }
37
+ export function getExclusiveConflicts(options) {
38
+ return options.reduce((acc, curr) => {
39
+ acc[curr] = options.filter(s => s !== curr);
40
+ return acc;
41
+ }, {});
42
+ }
@@ -1,16 +1,18 @@
1
1
  import { UploadProjectTools } from './project/UploadProjectTools.js';
2
2
  import { CreateProjectTool } from './project/CreateProjectTool.js';
3
3
  import { GuidedWalkthroughTool } from './project/GuidedWalkthroughTool.js';
4
- import { DeployProject } from './project/DeployProject.js';
5
- import { AddFeatureToProject } from './project/AddFeatureToProject.js';
4
+ import { DeployProjectTool } from './project/DeployProjectTool.js';
5
+ import { AddFeatureToProjectTool } from './project/AddFeatureToProjectTool.js';
6
6
  import { ValidateProjectTool } from './project/ValidateProjectTool.js';
7
+ import { GetConfigValuesTool } from './project/GetConfigValuesTool.js';
7
8
  export function registerProjectTools(mcpServer) {
8
9
  return [
9
10
  new UploadProjectTools(mcpServer).register(),
10
11
  new CreateProjectTool(mcpServer).register(),
11
12
  new GuidedWalkthroughTool(mcpServer).register(),
12
- new DeployProject(mcpServer).register(),
13
- new AddFeatureToProject(mcpServer).register(),
13
+ new DeployProjectTool(mcpServer).register(),
14
+ new AddFeatureToProjectTool(mcpServer).register(),
14
15
  new ValidateProjectTool(mcpServer).register(),
16
+ new GetConfigValuesTool(mcpServer).register(),
15
17
  ];
16
18
  }
@@ -6,22 +6,22 @@ declare const inputSchemaZodObject: z.ZodObject<{
6
6
  addApp: z.ZodBoolean;
7
7
  distribution: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"marketplace">, z.ZodLiteral<"private">]>>;
8
8
  auth: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"static">, z.ZodLiteral<"oauth">]>>;
9
- features: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">]>, "many">>;
9
+ features: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">]>, "many">>;
10
10
  }, "strip", z.ZodTypeAny, {
11
11
  absoluteProjectPath: string;
12
12
  addApp: boolean;
13
13
  auth?: "oauth" | "static" | undefined;
14
14
  distribution?: "marketplace" | "private" | undefined;
15
- features?: ("card" | "settings" | "app-function" | "webhooks")[] | undefined;
15
+ features?: ("card" | "settings" | "app-function" | "webhooks" | "workflow-action")[] | undefined;
16
16
  }, {
17
17
  absoluteProjectPath: string;
18
18
  addApp: boolean;
19
19
  auth?: "oauth" | "static" | undefined;
20
20
  distribution?: "marketplace" | "private" | undefined;
21
- features?: ("card" | "settings" | "app-function" | "webhooks")[] | undefined;
21
+ features?: ("card" | "settings" | "app-function" | "webhooks" | "workflow-action")[] | undefined;
22
22
  }>;
23
23
  export type AddFeatureInputSchema = z.infer<typeof inputSchemaZodObject>;
24
- export declare class AddFeatureToProject extends Tool<AddFeatureInputSchema> {
24
+ export declare class AddFeatureToProjectTool extends Tool<AddFeatureInputSchema> {
25
25
  constructor(mcpServer: McpServer);
26
26
  handler({ absoluteProjectPath, distribution, auth, features, addApp, }: AddFeatureInputSchema): Promise<TextContentResponse>;
27
27
  register(): RegisteredTool;
@@ -2,8 +2,8 @@ import { Tool } from '../../types.js';
2
2
  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
- import { absoluteProjectPath } from './constants.js';
6
- import { runCommandInDir } from '../../utils/project.js';
5
+ import { absoluteProjectPath, features } from './constants.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
  const inputSchema = {
@@ -23,23 +23,14 @@ const inputSchema = {
23
23
  z.literal(APP_AUTH_TYPES.OAUTH),
24
24
  ]))
25
25
  .describe('Static uses a static non changing authentication token, and is only available for private distribution. If not specified by the user, do not choose for them. This cannot be changed after a project is uploaded.'),
26
- features: z
27
- .array(z
28
- .union([
29
- z.literal('card'),
30
- z.literal('settings'),
31
- z.literal('app-function'),
32
- z.literal('webhooks'),
33
- ])
34
- .describe('The features to include in the project, multiple options can be selected'))
35
- .optional(),
26
+ features,
36
27
  };
37
28
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
29
  const inputSchemaZodObject = z.object({
39
30
  ...inputSchema,
40
31
  });
41
32
  const toolName = 'add-feature-to-hubspot-project';
42
- export class AddFeatureToProject extends Tool {
33
+ export class AddFeatureToProjectTool extends Tool {
43
34
  constructor(mcpServer) {
44
35
  super(mcpServer);
45
36
  }
@@ -77,7 +68,8 @@ export class AddFeatureToProject extends Tool {
77
68
  register() {
78
69
  return this.mcpServer.registerTool(toolName, {
79
70
  title: 'Add feature to HubSpot Project',
80
- description: 'Adds a feature to an existing HubSpot project',
71
+ description: `Adds a feature to an existing HubSpot project.
72
+ Only works for projects with platformVersion '2025.2' and beyond`,
81
73
  inputSchema,
82
74
  }, this.handler);
83
75
  }
@@ -8,7 +8,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
8
8
  projectBase: z.ZodUnion<[z.ZodLiteral<"empty">, z.ZodLiteral<"app">]>;
9
9
  distribution: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"marketplace">, z.ZodLiteral<"private">]>>;
10
10
  auth: z.ZodOptional<z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"static">, z.ZodLiteral<"oauth">]>>>;
11
- features: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">]>, "many">>;
11
+ features: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodLiteral<"card">, z.ZodLiteral<"settings">, z.ZodLiteral<"app-function">, z.ZodLiteral<"webhooks">, z.ZodLiteral<"workflow-action">]>, "many">>;
12
12
  }, "strip", z.ZodTypeAny, {
13
13
  projectBase: "app" | "empty";
14
14
  absoluteCurrentWorkingDirectory: string;
@@ -16,7 +16,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
16
16
  name?: string | undefined;
17
17
  auth?: "oauth" | "static" | undefined;
18
18
  distribution?: "marketplace" | "private" | undefined;
19
- features?: ("card" | "settings" | "app-function" | "webhooks")[] | undefined;
19
+ features?: ("card" | "settings" | "app-function" | "webhooks" | "workflow-action")[] | undefined;
20
20
  }, {
21
21
  projectBase: "app" | "empty";
22
22
  absoluteCurrentWorkingDirectory: string;
@@ -24,7 +24,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
24
24
  name?: string | undefined;
25
25
  auth?: "oauth" | "static" | undefined;
26
26
  distribution?: "marketplace" | "private" | undefined;
27
- features?: ("card" | "settings" | "app-function" | "webhooks")[] | undefined;
27
+ features?: ("card" | "settings" | "app-function" | "webhooks" | "workflow-action")[] | undefined;
28
28
  }>;
29
29
  export type CreateProjectInputSchema = z.infer<typeof inputSchemaZodObject>;
30
30
  export declare class CreateProjectTool extends Tool<CreateProjectInputSchema> {
@@ -1,10 +1,9 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
- import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../lib/constants.js';
3
+ import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../lib/constants.js';
4
4
  import { addFlag } from '../../utils/command.js';
5
- import { EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../lib/projects/create/v3.js';
6
- import { absoluteCurrentWorkingDirectory } from './constants.js';
7
- import { runCommandInDir } from '../../utils/project.js';
5
+ import { absoluteCurrentWorkingDirectory, features } from './constants.js';
6
+ import { runCommandInDir } from '../../utils/command.js';
8
7
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
9
8
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
10
9
  const inputSchema = {
@@ -32,16 +31,7 @@ const inputSchema = {
32
31
  ]))
33
32
  .describe('Static uses a static non changing authentication token, and is only available for private distribution. If not specified by the user, do not choose for them. This cannot be changed after a project is uploaded.')
34
33
  .optional(),
35
- features: z
36
- .array(z
37
- .union([
38
- z.literal('card'),
39
- z.literal('settings'),
40
- z.literal('app-function'),
41
- z.literal('webhooks'),
42
- ])
43
- .describe('The features to include in the project, multiple options can be selected'))
44
- .optional(),
34
+ features,
45
35
  };
46
36
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
47
37
  const inputSchemaZodObject = z.object({ ...inputSchema });
@@ -12,7 +12,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
12
12
  buildNumber?: number | undefined;
13
13
  }>;
14
14
  type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
15
- export declare class DeployProject extends Tool<InputSchemaType> {
15
+ export declare class DeployProjectTool extends Tool<InputSchemaType> {
16
16
  constructor(mcpServer: McpServer);
17
17
  handler({ absoluteProjectPath, buildNumber, }: InputSchemaType): Promise<TextContentResponse>;
18
18
  register(): RegisteredTool;
@@ -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 { absoluteProjectPath } from './constants.js';
5
- import { runCommandInDir } from '../../utils/project.js';
5
+ import { runCommandInDir } from '../../utils/command.js';
6
6
  import { formatTextContents, formatTextContent } from '../../utils/content.js';
7
7
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
8
  const inputSchema = {
@@ -16,7 +16,7 @@ const inputSchemaZodObject = z.object({
16
16
  ...inputSchema,
17
17
  });
18
18
  const toolName = 'deploy-hubspot-project';
19
- export class DeployProject extends Tool {
19
+ export class DeployProjectTool extends Tool {
20
20
  constructor(mcpServer) {
21
21
  super(mcpServer);
22
22
  }
@@ -0,0 +1,20 @@
1
+ import { TextContentResponse, Tool } from '../../types.js';
2
+ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ declare const inputSchemaZodObject: z.ZodObject<{
5
+ platformVersion: z.ZodString;
6
+ featureType: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ platformVersion: string;
9
+ featureType: string;
10
+ }, {
11
+ platformVersion: string;
12
+ featureType: string;
13
+ }>;
14
+ type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
15
+ export declare class GetConfigValuesTool extends Tool<InputSchemaType> {
16
+ constructor(mcpServer: McpServer);
17
+ handler({ platformVersion, featureType, }: InputSchemaType): Promise<TextContentResponse>;
18
+ register(): RegisteredTool;
19
+ }
20
+ export {};
@@ -0,0 +1,51 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { formatTextContents } from '../../utils/content.js';
4
+ import { getIntermediateRepresentationSchema, mapToInternalType, } from '@hubspot/project-parsing-lib';
5
+ import { getAccountId } from '@hubspot/local-dev-lib/config';
6
+ import { useV3Api } from '../../../lib/projects/buildAndDeploy.js';
7
+ const inputSchema = {
8
+ platformVersion: z
9
+ .string()
10
+ .describe('The platform version for the project. Located in the hsproject.json file.'),
11
+ featureType: z
12
+ .string()
13
+ .describe('The type of the component to fetch the JSON schema for. This will be the `type` field in the -hsmeta.json file'),
14
+ };
15
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
+ const inputSchemaZodObject = z.object({
17
+ ...inputSchema,
18
+ });
19
+ const toolName = 'get-hubspot-project-feature-config-schema';
20
+ export class GetConfigValuesTool extends Tool {
21
+ constructor(mcpServer) {
22
+ super(mcpServer);
23
+ }
24
+ async handler({ platformVersion, featureType, }) {
25
+ try {
26
+ if (!useV3Api(platformVersion)) {
27
+ return formatTextContents(`Can only be used on projects with a minimum platformVersion of 2025.2`);
28
+ }
29
+ const schema = await getIntermediateRepresentationSchema({
30
+ platformVersion,
31
+ projectSourceDir: '',
32
+ accountId: getAccountId(),
33
+ });
34
+ const internalComponentType = mapToInternalType(featureType);
35
+ if (schema[internalComponentType]) {
36
+ return formatTextContents(JSON.stringify({ config: schema[internalComponentType] }));
37
+ }
38
+ }
39
+ catch (error) { }
40
+ return formatTextContents(`Unable to locate JSON schema for type ${featureType}`);
41
+ }
42
+ register() {
43
+ return this.mcpServer.registerTool(toolName, {
44
+ title: 'Fetch the JSON Schema for component',
45
+ description: `Fetches and returns the JSON schema for the provided feature 'type' found in -hsmeta.json file.
46
+ This should be called before editing a '-hsmeta.json' file to get the list of possible values and restrictions on those values.
47
+ This will only work for projects with platformVersion 2025.2 and beyond`,
48
+ inputSchema,
49
+ }, this.handler);
50
+ }
51
+ }
@@ -1,5 +1,5 @@
1
1
  import { Tool } from '../../types.js';
2
- import { runCommandInDir } from '../../utils/project.js';
2
+ import { runCommandInDir } from '../../utils/command.js';
3
3
  import { absoluteProjectPath } from './constants.js';
4
4
  import z from 'zod';
5
5
  import { formatTextContents } from '../../utils/content.js';
@@ -1,7 +1,7 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
3
  import { absoluteProjectPath } from './constants.js';
4
- import { runCommandInDir } from '../../utils/project.js';
4
+ import { runCommandInDir } from '../../utils/command.js';
5
5
  import { formatTextContents } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  const inputSchema = {
@@ -1,9 +1,9 @@
1
- import { AddFeatureToProject, } from '../AddFeatureToProject.js';
2
- import { runCommandInDir } from '../../../utils/project.js';
1
+ import { AddFeatureToProjectTool, } from '../AddFeatureToProjectTool.js';
2
+ import { runCommandInDir } from '../../../utils/command.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
4
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../../lib/constants.js';
5
5
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
- vi.mock('../../../utils/project');
6
+ vi.mock('../../../utils/command');
7
7
  vi.mock('../../../utils/command');
8
8
  vi.mock('../../../../lib/constants');
9
9
  vi.mock('../../../utils/toolUsageTracking');
@@ -21,18 +21,18 @@ describe('mcp-server/tools/project/AddFeatureToProject', () => {
21
21
  };
22
22
  mockRegisteredTool = {};
23
23
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
24
- tool = new AddFeatureToProject(mockMcpServer);
24
+ tool = new AddFeatureToProjectTool(mockMcpServer);
25
25
  // Mock addFlag to simulate command building
26
26
  mockAddFlag.mockImplementation((command, flag, value) => `${command} --${flag} "${value}"`);
27
27
  });
28
28
  describe('register', () => {
29
29
  it('should register tool with correct parameters', () => {
30
30
  const result = tool.register();
31
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('add-feature-to-hubspot-project', {
31
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('add-feature-to-hubspot-project', expect.objectContaining({
32
32
  title: 'Add feature to HubSpot Project',
33
- description: 'Adds a feature to an existing HubSpot project',
33
+ description: expect.stringContaining('Adds a feature to an existing HubSpot project'),
34
34
  inputSchema: expect.any(Object),
35
- }, tool.handler);
35
+ }), tool.handler);
36
36
  expect(result).toBe(mockRegisteredTool);
37
37
  });
38
38
  });
@@ -1,10 +1,9 @@
1
1
  import { CreateProjectTool, } from '../CreateProjectTool.js';
2
- import { runCommandInDir } from '../../../utils/project.js';
2
+ import { runCommandInDir } from '../../../utils/command.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
- import { APP_DISTRIBUTION_TYPES } from '../../../../lib/constants.js';
5
- import { EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../../lib/projects/create/v3.js';
4
+ import { APP_DISTRIBUTION_TYPES, EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../../lib/constants.js';
6
5
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
7
- vi.mock('../../../utils/project');
6
+ vi.mock('../../../utils/command');
8
7
  vi.mock('../../../utils/command');
9
8
  vi.mock('../../../../lib/constants');
10
9
  vi.mock('../../../../lib/projects/create/v3');
@@ -1,8 +1,8 @@
1
- import { DeployProject } from '../DeployProject.js';
2
- import { runCommandInDir } from '../../../utils/project.js';
1
+ import { DeployProjectTool } from '../DeployProjectTool.js';
2
+ import { runCommandInDir } from '../../../utils/command.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
4
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
5
- vi.mock('../../../utils/project');
5
+ vi.mock('../../../utils/command');
6
6
  vi.mock('../../../utils/command');
7
7
  vi.mock('../../../utils/toolUsageTracking');
8
8
  const mockRunCommandInDir = runCommandInDir;
@@ -19,7 +19,7 @@ describe('mcp-server/tools/project/DeployProject', () => {
19
19
  };
20
20
  mockRegisteredTool = {};
21
21
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
22
- tool = new DeployProject(mockMcpServer);
22
+ tool = new DeployProjectTool(mockMcpServer);
23
23
  // Mock addFlag to simulate command building
24
24
  mockAddFlag.mockImplementation((command, flag, value) => `${command} --${flag} "${value}"`);
25
25
  });
@@ -0,0 +1,198 @@
1
+ import { GetConfigValuesTool } from '../GetConfigValuesTool.js';
2
+ import { getIntermediateRepresentationSchema, mapToInternalType, } from '@hubspot/project-parsing-lib';
3
+ import { getAccountId } from '@hubspot/local-dev-lib/config';
4
+ vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
5
+ vi.mock('@hubspot/project-parsing-lib');
6
+ vi.mock('@hubspot/local-dev-lib/config');
7
+ vi.mock('../../../utils/toolUsageTracking');
8
+ const mockGetIntermediateRepresentationSchema = getIntermediateRepresentationSchema;
9
+ const mockMapToInternalType = mapToInternalType;
10
+ const mockGetAccountId = getAccountId;
11
+ describe('mcp-server/tools/project/GetConfigValuesTool', () => {
12
+ let mockMcpServer;
13
+ let tool;
14
+ let mockRegisteredTool;
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ // @ts-expect-error Not mocking the whole thing
18
+ mockMcpServer = {
19
+ registerTool: vi.fn(),
20
+ };
21
+ mockRegisteredTool = {};
22
+ mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
23
+ tool = new GetConfigValuesTool(mockMcpServer);
24
+ });
25
+ describe('register', () => {
26
+ it('should register tool with correct parameters', () => {
27
+ const result = tool.register();
28
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-hubspot-project-feature-config-schema', {
29
+ title: 'Fetch the JSON Schema for component',
30
+ description: expect.stringContaining('Fetches and returns the JSON schema for the provided feature'),
31
+ inputSchema: expect.objectContaining({
32
+ platformVersion: expect.objectContaining({
33
+ describe: expect.any(Function),
34
+ }),
35
+ featureType: expect.objectContaining({
36
+ describe: expect.any(Function),
37
+ }),
38
+ }),
39
+ }, tool.handler);
40
+ expect(result).toBe(mockRegisteredTool);
41
+ });
42
+ });
43
+ describe('handler', () => {
44
+ const input = {
45
+ platformVersion: '2025.2',
46
+ featureType: 'card',
47
+ };
48
+ beforeEach(() => {
49
+ mockGetAccountId.mockReturnValue(123456789);
50
+ });
51
+ it('should return config schema when component type exists', async () => {
52
+ const mockSchema = {
53
+ 'internal-card-type': {
54
+ type: 'object',
55
+ properties: {
56
+ title: { type: 'string' },
57
+ description: { type: 'string' },
58
+ },
59
+ },
60
+ };
61
+ mockGetIntermediateRepresentationSchema.mockResolvedValue(mockSchema);
62
+ mockMapToInternalType.mockReturnValue('internal-card-type');
63
+ const result = await tool.handler(input);
64
+ expect(mockGetIntermediateRepresentationSchema).toHaveBeenCalledWith({
65
+ platformVersion: '2025.2',
66
+ projectSourceDir: '',
67
+ accountId: 123456789,
68
+ });
69
+ expect(mockMapToInternalType).toHaveBeenCalledWith('card');
70
+ expect(result).toEqual({
71
+ content: [
72
+ {
73
+ type: 'text',
74
+ text: JSON.stringify({
75
+ config: {
76
+ type: 'object',
77
+ properties: {
78
+ title: { type: 'string' },
79
+ description: { type: 'string' },
80
+ },
81
+ },
82
+ }),
83
+ },
84
+ ],
85
+ });
86
+ });
87
+ it('should return error message when component type does not exist in schema', async () => {
88
+ const mockSchema = {
89
+ 'other-type': {
90
+ type: 'object',
91
+ properties: {},
92
+ },
93
+ };
94
+ mockGetIntermediateRepresentationSchema.mockResolvedValue(mockSchema);
95
+ mockMapToInternalType.mockReturnValue('internal-card-type');
96
+ const result = await tool.handler(input);
97
+ expect(result).toEqual({
98
+ content: [
99
+ {
100
+ type: 'text',
101
+ text: 'Unable to locate JSON schema for type card',
102
+ },
103
+ ],
104
+ });
105
+ });
106
+ it('should return error message when getIntermediateRepresentationSchema throws', async () => {
107
+ mockGetIntermediateRepresentationSchema.mockRejectedValue(new Error('Schema fetch failed'));
108
+ mockMapToInternalType.mockReturnValue('internal-card-type');
109
+ const result = await tool.handler(input);
110
+ expect(result).toEqual({
111
+ content: [
112
+ {
113
+ type: 'text',
114
+ text: 'Unable to locate JSON schema for type card',
115
+ },
116
+ ],
117
+ });
118
+ });
119
+ it('should return error message when mapToInternalType throws', async () => {
120
+ const mockSchema = {};
121
+ mockGetIntermediateRepresentationSchema.mockResolvedValue(mockSchema);
122
+ mockMapToInternalType.mockImplementation(() => {
123
+ throw new Error('Mapping failed');
124
+ });
125
+ const result = await tool.handler(input);
126
+ expect(result).toEqual({
127
+ content: [
128
+ {
129
+ type: 'text',
130
+ text: 'Unable to locate JSON schema for type card',
131
+ },
132
+ ],
133
+ });
134
+ });
135
+ it('should handle null account id', async () => {
136
+ mockGetAccountId.mockReturnValue(null);
137
+ mockGetIntermediateRepresentationSchema.mockRejectedValue(new Error('No account ID'));
138
+ const result = await tool.handler(input);
139
+ expect(result).toEqual({
140
+ content: [
141
+ {
142
+ type: 'text',
143
+ text: 'Unable to locate JSON schema for type card',
144
+ },
145
+ ],
146
+ });
147
+ });
148
+ it('should handle empty schema object', async () => {
149
+ const mockSchema = {};
150
+ mockGetIntermediateRepresentationSchema.mockResolvedValue(mockSchema);
151
+ mockMapToInternalType.mockReturnValue('internal-card-type');
152
+ const result = await tool.handler(input);
153
+ expect(result).toEqual({
154
+ content: [
155
+ {
156
+ type: 'text',
157
+ text: 'Unable to locate JSON schema for type card',
158
+ },
159
+ ],
160
+ });
161
+ });
162
+ it('should handle complex nested schema structures', async () => {
163
+ const complexSchema = {
164
+ 'internal-card-type': {
165
+ type: 'object',
166
+ properties: {
167
+ title: { type: 'string', maxLength: 100 },
168
+ metadata: {
169
+ type: 'object',
170
+ properties: {
171
+ author: { type: 'string' },
172
+ tags: {
173
+ type: 'array',
174
+ items: { type: 'string' },
175
+ },
176
+ },
177
+ required: ['author'],
178
+ },
179
+ },
180
+ required: ['title'],
181
+ },
182
+ };
183
+ mockGetIntermediateRepresentationSchema.mockResolvedValue(complexSchema);
184
+ mockMapToInternalType.mockReturnValue('internal-card-type');
185
+ const result = await tool.handler(input);
186
+ expect(result).toEqual({
187
+ content: [
188
+ {
189
+ type: 'text',
190
+ text: JSON.stringify({
191
+ config: complexSchema['internal-card-type'],
192
+ }),
193
+ },
194
+ ],
195
+ });
196
+ });
197
+ });
198
+ });
@@ -1,7 +1,7 @@
1
1
  import { UploadProjectTools } from '../UploadProjectTools.js';
2
- import { runCommandInDir } from '../../../utils/project.js';
2
+ import { runCommandInDir } from '../../../utils/command.js';
3
3
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
4
- vi.mock('../../../utils/project');
4
+ vi.mock('../../../utils/command');
5
5
  vi.mock('../../../utils/toolUsageTracking');
6
6
  const mockRunCommandInDir = runCommandInDir;
7
7
  describe('mcp-server/tools/project/UploadProjectTools', () => {
@@ -1,7 +1,7 @@
1
1
  import { ValidateProjectTool, } from '../ValidateProjectTool.js';
2
- import { runCommandInDir } from '../../../utils/project.js';
2
+ import { runCommandInDir } from '../../../utils/command.js';
3
3
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
4
- vi.mock('../../../utils/project');
4
+ vi.mock('../../../utils/command');
5
5
  vi.mock('../../../utils/toolUsageTracking');
6
6
  const mockRunCommandInDir = runCommandInDir;
7
7
  describe('mcp-server/tools/project/ValidateProjectTool', () => {