@hubspot/cli 7.9.0-beta.1 → 7.9.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 (83) hide show
  1. package/commands/project/__tests__/deploy.test.js +4 -3
  2. package/lang/en.d.ts +10 -10
  3. package/lang/en.js +13 -13
  4. package/lib/__tests__/npm.test.js +1 -1
  5. package/lib/__tests__/sandboxSync.test.js +1 -1
  6. package/lib/__tests__/usageTracking.test.js +2 -2
  7. package/lib/configMigrate.js +3 -3
  8. package/lib/doctor/DiagnosticInfoBuilder.js +1 -1
  9. package/lib/doctor/Doctor.js +1 -1
  10. package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +4 -2
  11. package/lib/doctor/__tests__/Doctor.test.js +1 -1
  12. package/lib/jsonLoader.d.ts +14 -0
  13. package/lib/jsonLoader.js +60 -0
  14. package/lib/middleware/__test__/requestMiddleware.test.js +1 -1
  15. package/lib/middleware/autoUpdateMiddleware.js +1 -1
  16. package/lib/middleware/commandTargetingUtils.js +1 -0
  17. package/lib/middleware/fireAlarmMiddleware.js +1 -1
  18. package/lib/middleware/notificationsMiddleware.js +1 -1
  19. package/lib/middleware/requestMiddleware.js +1 -1
  20. package/lib/npm.js +1 -1
  21. package/lib/projects/__tests__/AppDevModeInterface.test.js +3 -0
  22. package/lib/projects/create/__tests__/v2.test.js +20 -14
  23. package/lib/projects/create/v2.js +8 -13
  24. package/lib/projects/localDev/LocalDevLogger.js +2 -2
  25. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
  26. package/lib/projects/localDev/LocalDevWebsocketServer.js +1 -1
  27. package/lib/prompts/promptUtils.d.ts +8 -0
  28. package/lib/prompts/promptUtils.js +7 -1
  29. package/lib/prompts/selectProjectTemplatePrompt.js +4 -0
  30. package/lib/sandboxSync.js +1 -1
  31. package/lib/usageTracking.js +2 -2
  32. package/mcp-server/tools/cms/HsCreateFunctionTool.js +2 -2
  33. package/mcp-server/tools/cms/HsCreateModuleTool.js +2 -2
  34. package/mcp-server/tools/cms/HsCreateTemplateTool.js +2 -2
  35. package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -9
  36. package/mcp-server/tools/cms/HsListFunctionsTool.js +1 -1
  37. package/mcp-server/tools/cms/HsListTool.js +1 -1
  38. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +7 -4
  39. package/mcp-server/tools/cms/__tests__/HsCreateModuleTool.test.js +7 -3
  40. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +7 -4
  41. package/mcp-server/tools/cms/__tests__/HsFunctionLogsTool.test.js +5 -1
  42. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +8 -3
  43. package/mcp-server/tools/cms/__tests__/HsListTool.test.js +8 -3
  44. package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +4 -1
  45. package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -5
  46. package/mcp-server/tools/project/CreateProjectTool.js +2 -2
  47. package/mcp-server/tools/project/DeployProjectTool.d.ts +4 -1
  48. package/mcp-server/tools/project/DeployProjectTool.js +4 -3
  49. package/mcp-server/tools/project/DocFetchTool.d.ts +4 -1
  50. package/mcp-server/tools/project/DocFetchTool.js +7 -6
  51. package/mcp-server/tools/project/DocsSearchTool.js +5 -5
  52. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +4 -1
  53. package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +7 -5
  54. package/mcp-server/tools/project/GetApplicationInfoTool.d.ts +8 -2
  55. package/mcp-server/tools/project/GetApplicationInfoTool.js +7 -6
  56. package/mcp-server/tools/project/GetConfigValuesTool.js +4 -4
  57. package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +4 -1
  58. package/mcp-server/tools/project/GuidedWalkthroughTool.js +6 -14
  59. package/mcp-server/tools/project/UploadProjectTools.d.ts +4 -1
  60. package/mcp-server/tools/project/UploadProjectTools.js +4 -3
  61. package/mcp-server/tools/project/ValidateProjectTool.d.ts +4 -1
  62. package/mcp-server/tools/project/ValidateProjectTool.js +5 -4
  63. package/mcp-server/tools/project/__tests__/AddFeatureToProjectTool.test.js +6 -1
  64. package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +7 -3
  65. package/mcp-server/tools/project/__tests__/DeployProjectTool.test.js +7 -2
  66. package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +8 -3
  67. package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +6 -2
  68. package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +8 -3
  69. package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +9 -5
  70. package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +6 -2
  71. package/mcp-server/tools/project/__tests__/GuidedWalkthroughTool.test.js +43 -13
  72. package/mcp-server/tools/project/__tests__/UploadProjectTools.test.js +8 -2
  73. package/mcp-server/tools/project/__tests__/ValidateProjectTool.test.js +8 -2
  74. package/mcp-server/utils/__tests__/content.test.d.ts +1 -0
  75. package/mcp-server/utils/__tests__/content.test.js +166 -0
  76. package/mcp-server/utils/__tests__/feedbackTracking.test.d.ts +1 -0
  77. package/mcp-server/utils/__tests__/feedbackTracking.test.js +121 -0
  78. package/mcp-server/utils/content.d.ts +1 -1
  79. package/mcp-server/utils/content.js +8 -1
  80. package/mcp-server/utils/feedbackTracking.d.ts +1 -0
  81. package/mcp-server/utils/feedbackTracking.js +41 -0
  82. package/package.json +2 -2
  83. package/commands/project/__tests__/fixtures/exampleProject.json +0 -33
@@ -2,22 +2,25 @@ import { TextContentResponse, Tool } from '../../types.js';
2
2
  import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { z } from 'zod';
4
4
  declare const inputSchemaZodObject: z.ZodObject<{
5
+ absoluteCurrentWorkingDirectory: z.ZodString;
5
6
  appId: z.ZodString;
6
7
  startDate: z.ZodOptional<z.ZodString>;
7
8
  endDate: z.ZodOptional<z.ZodString>;
8
9
  }, "strip", z.ZodTypeAny, {
9
10
  appId: string;
11
+ absoluteCurrentWorkingDirectory: string;
10
12
  startDate?: string | undefined;
11
13
  endDate?: string | undefined;
12
14
  }, {
13
15
  appId: string;
16
+ absoluteCurrentWorkingDirectory: string;
14
17
  startDate?: string | undefined;
15
18
  endDate?: string | undefined;
16
19
  }>;
17
20
  export type GetApiUsagePatternsByAppIdInputSchema = z.infer<typeof inputSchemaZodObject>;
18
21
  export declare class GetApiUsagePatternsByAppIdTool extends Tool<GetApiUsagePatternsByAppIdInputSchema> {
19
22
  constructor(mcpServer: McpServer);
20
- handler({ appId, startDate, endDate, }: GetApiUsagePatternsByAppIdInputSchema): Promise<TextContentResponse>;
23
+ handler({ absoluteCurrentWorkingDirectory, appId, startDate, endDate, }: GetApiUsagePatternsByAppIdInputSchema): Promise<TextContentResponse>;
21
24
  register(): RegisteredTool;
22
25
  }
23
26
  export {};
@@ -5,7 +5,9 @@ import { http } from '@hubspot/local-dev-lib/http';
5
5
  import { formatTextContents } from '../../utils/content.js';
6
6
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
7
7
  import { getAccountId } from '@hubspot/local-dev-lib/config';
8
+ import { absoluteCurrentWorkingDirectory } from './constants.js';
8
9
  const inputSchema = {
10
+ absoluteCurrentWorkingDirectory,
9
11
  appId: z
10
12
  .string()
11
13
  .regex(/^\d+$/, 'App ID must be a numeric string')
@@ -28,14 +30,14 @@ export class GetApiUsagePatternsByAppIdTool extends Tool {
28
30
  constructor(mcpServer) {
29
31
  super(mcpServer);
30
32
  }
31
- async handler({ appId, startDate, endDate, }) {
33
+ async handler({ absoluteCurrentWorkingDirectory, appId, startDate, endDate, }) {
32
34
  await trackToolUsage(toolName);
33
35
  try {
34
36
  // Get account ID from CLI config
35
37
  const accountId = getAccountId();
36
38
  if (!accountId) {
37
39
  const authErrorMessage = `No account ID found. Please run \`hs account auth\` to configure an account, or set a default account with \`hs account use <account>\``;
38
- return formatTextContents(authErrorMessage);
40
+ return formatTextContents(absoluteCurrentWorkingDirectory, authErrorMessage);
39
41
  }
40
42
  const response = await http.get(accountId, {
41
43
  url: `app/feature/utilization/public/v3/insights/app/${appId}/usage-patterns`,
@@ -47,15 +49,15 @@ export class GetApiUsagePatternsByAppIdTool extends Tool {
47
49
  // Format the response for display
48
50
  const { data } = response;
49
51
  const formattedResult = JSON.stringify(data, null, 2);
50
- return formatTextContents(formattedResult);
52
+ return formatTextContents(absoluteCurrentWorkingDirectory, formattedResult);
51
53
  }
52
54
  catch (error) {
53
55
  if (isHubSpotHttpError(error)) {
54
56
  // Handle HubSpot-specific HTTP errors
55
- return formatTextContents(error.toString());
57
+ return formatTextContents(absoluteCurrentWorkingDirectory, error.toString());
56
58
  }
57
59
  const errorMessage = `${error instanceof Error ? error.message : String(error)}`;
58
- return formatTextContents(errorMessage);
60
+ return formatTextContents(absoluteCurrentWorkingDirectory, errorMessage);
59
61
  }
60
62
  }
61
63
  register() {
@@ -1,11 +1,17 @@
1
1
  import { TextContentResponse, Tool } from '../../types.js';
2
2
  import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { z } from 'zod';
4
- declare const inputSchemaZodObject: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
4
+ declare const inputSchemaZodObject: z.ZodObject<{
5
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ absoluteCurrentWorkingDirectory: string;
8
+ }, {
9
+ absoluteCurrentWorkingDirectory: string;
10
+ }>;
5
11
  export type GetApplicationInfoInputSchema = z.infer<typeof inputSchemaZodObject>;
6
12
  export declare class GetApplicationInfoTool extends Tool<GetApplicationInfoInputSchema> {
7
13
  constructor(mcpServer: McpServer);
8
- handler({}: GetApplicationInfoInputSchema): Promise<TextContentResponse>;
14
+ handler({ absoluteCurrentWorkingDirectory, }: GetApplicationInfoInputSchema): Promise<TextContentResponse>;
9
15
  register(): RegisteredTool;
10
16
  }
11
17
  export {};
@@ -5,7 +5,8 @@ import { http } from '@hubspot/local-dev-lib/http';
5
5
  import { formatTextContents } from '../../utils/content.js';
6
6
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
7
7
  import { getAccountId } from '@hubspot/local-dev-lib/config';
8
- const inputSchema = {};
8
+ import { absoluteCurrentWorkingDirectory } from './constants.js';
9
+ const inputSchema = { absoluteCurrentWorkingDirectory };
9
10
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
11
  const inputSchemaZodObject = z.object({ ...inputSchema });
11
12
  const toolName = 'get-applications-info';
@@ -13,14 +14,14 @@ export class GetApplicationInfoTool extends Tool {
13
14
  constructor(mcpServer) {
14
15
  super(mcpServer);
15
16
  }
16
- async handler({}) {
17
+ async handler({ absoluteCurrentWorkingDirectory, }) {
17
18
  await trackToolUsage(toolName);
18
19
  try {
19
20
  // Get account ID from CLI config
20
21
  const accountId = getAccountId();
21
22
  if (!accountId) {
22
23
  const authErrorMessage = `No account ID found. Please run \`hs account auth\` to configure an account, or set a default account with \`hs account use <account>\``;
23
- return formatTextContents(authErrorMessage);
24
+ return formatTextContents(absoluteCurrentWorkingDirectory, authErrorMessage);
24
25
  }
25
26
  const response = await http.get(accountId, {
26
27
  url: `app/feature/utilization/public/v3/insights/apps`,
@@ -28,15 +29,15 @@ export class GetApplicationInfoTool extends Tool {
28
29
  // Format the response for display
29
30
  const { data } = response;
30
31
  const formattedResult = JSON.stringify(data, null, 2);
31
- return formatTextContents(formattedResult);
32
+ return formatTextContents(absoluteCurrentWorkingDirectory, formattedResult);
32
33
  }
33
34
  catch (error) {
34
35
  if (isHubSpotHttpError(error)) {
35
36
  // Handle HubSpot-specific HTTP errors
36
- return formatTextContents(error.toString());
37
+ return formatTextContents(absoluteCurrentWorkingDirectory, error.toString());
37
38
  }
38
39
  const errorMessage = `${error instanceof Error ? error.message : String(error)}`;
39
- return formatTextContents(errorMessage);
40
+ return formatTextContents(absoluteCurrentWorkingDirectory, errorMessage);
40
41
  }
41
42
  }
42
43
  register() {
@@ -26,12 +26,12 @@ export class GetConfigValuesTool extends Tool {
26
26
  async handler({ absoluteCurrentWorkingDirectory, platformVersion, featureType, }) {
27
27
  try {
28
28
  if (!isV2Project(platformVersion)) {
29
- return formatTextContents(`Can only be used on projects with a minimum platformVersion of 2025.2`);
29
+ return formatTextContents(absoluteCurrentWorkingDirectory, `Can only be used on projects with a minimum platformVersion of 2025.2`);
30
30
  }
31
31
  const accountId = getAccountIdFromCliConfig(absoluteCurrentWorkingDirectory);
32
32
  if (!accountId) {
33
33
  const authErrorMessage = `No account ID found. Please run \`hs account auth\` to configure an account, or set a default account with \`hs account use <account>\``;
34
- return formatTextContents(authErrorMessage);
34
+ return formatTextContents(absoluteCurrentWorkingDirectory, authErrorMessage);
35
35
  }
36
36
  const schema = await getIntermediateRepresentationSchema({
37
37
  platformVersion,
@@ -40,11 +40,11 @@ export class GetConfigValuesTool extends Tool {
40
40
  });
41
41
  const internalComponentType = mapToInternalType(featureType);
42
42
  if (schema[internalComponentType]) {
43
- return formatTextContents(JSON.stringify({ config: schema[internalComponentType] }));
43
+ return formatTextContents(absoluteCurrentWorkingDirectory, JSON.stringify({ config: schema[internalComponentType] }));
44
44
  }
45
45
  }
46
46
  catch (error) { }
47
- return formatTextContents(`Unable to locate JSON schema for type ${featureType}`);
47
+ return formatTextContents(absoluteCurrentWorkingDirectory, `Unable to locate JSON schema for type ${featureType}`);
48
48
  }
49
49
  register() {
50
50
  return this.mcpServer.registerTool(toolName, {
@@ -2,16 +2,19 @@ import { TextContentResponse, Tool } from '../../types.js';
2
2
  import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { z } from 'zod';
4
4
  declare const inputSchemaZodObject: z.ZodObject<{
5
+ absoluteCurrentWorkingDirectory: z.ZodString;
5
6
  command: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"hs init">, z.ZodLiteral<"hs auth">, z.ZodLiteral<"hs project create">, z.ZodLiteral<"hs project upload">]>>;
6
7
  }, "strip", z.ZodTypeAny, {
8
+ absoluteCurrentWorkingDirectory: string;
7
9
  command?: "hs auth" | "hs project create" | "hs project upload" | "hs init" | undefined;
8
10
  }, {
11
+ absoluteCurrentWorkingDirectory: string;
9
12
  command?: "hs auth" | "hs project create" | "hs project upload" | "hs init" | undefined;
10
13
  }>;
11
14
  type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
12
15
  export declare class GuidedWalkthroughTool extends Tool<InputSchemaType> {
13
16
  constructor(mcpServer: McpServer);
14
- handler({ command }: InputSchemaType): Promise<TextContentResponse>;
17
+ handler({ absoluteCurrentWorkingDirectory, command, }: InputSchemaType): Promise<TextContentResponse>;
15
18
  register(): RegisteredTool;
16
19
  }
17
20
  export {};
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  import { execAsync } from '../../utils/command.js';
4
4
  import { formatTextContents } from '../../utils/content.js';
5
5
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
6
+ import { absoluteCurrentWorkingDirectory } from './constants.js';
6
7
  const nextCommands = {
7
8
  'hs init': 'hs auth',
8
9
  'hs auth': 'hs project create',
@@ -10,6 +11,7 @@ const nextCommands = {
10
11
  'hs project upload': 'hs project dev',
11
12
  };
12
13
  const inputSchema = {
14
+ absoluteCurrentWorkingDirectory,
13
15
  command: z
14
16
  .union([
15
17
  z.literal('hs init'),
@@ -29,29 +31,19 @@ export class GuidedWalkthroughTool extends Tool {
29
31
  constructor(mcpServer) {
30
32
  super(mcpServer);
31
33
  }
32
- async handler({ command }) {
34
+ async handler({ absoluteCurrentWorkingDirectory, command, }) {
33
35
  await trackToolUsage(toolName);
34
36
  if (command) {
35
37
  const { stdout } = await execAsync(`${command} --help`);
36
- return formatTextContents(`Display this help output for the user amd wait for them to acknowledge: ${stdout}. ${nextCommands[command] ? `Once they are ready, A good command to look at next is ${nextCommands[command]}` : ''}`);
38
+ return formatTextContents(absoluteCurrentWorkingDirectory, `Display this help output for the user amd wait for them to acknowledge: ${stdout}. ${nextCommands[command] ? `Once they are ready, A good command to look at next is ${nextCommands[command]}` : ''}`);
37
39
  }
38
- return formatTextContents('Is there another command you would like to learn more about?');
40
+ return formatTextContents(absoluteCurrentWorkingDirectory, 'Is there another command you would like to learn more about?');
39
41
  }
40
42
  register() {
41
43
  return this.mcpServer.registerTool(toolName, {
42
44
  title: 'Guided walkthrough of the CLI',
43
45
  description: 'Give the user a guided walkthrough of the HubSpot CLI.',
44
- inputSchema: {
45
- command: z
46
- .union([
47
- z.literal('hs init'),
48
- z.literal('hs auth'),
49
- z.literal('hs project create'),
50
- z.literal('hs project upload'),
51
- ])
52
- .describe('The command to learn more about. Start with `hs init`')
53
- .optional(),
54
- },
46
+ inputSchema,
55
47
  }, this.handler);
56
48
  }
57
49
  }
@@ -3,15 +3,18 @@ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.
3
3
  import z from 'zod';
4
4
  declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteProjectPath: z.ZodString;
6
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
7
  }, "strip", z.ZodTypeAny, {
7
8
  absoluteProjectPath: string;
9
+ absoluteCurrentWorkingDirectory: string;
8
10
  }, {
9
11
  absoluteProjectPath: string;
12
+ absoluteCurrentWorkingDirectory: string;
10
13
  }>;
11
14
  type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
12
15
  export declare class UploadProjectTools extends Tool<InputSchemaType> {
13
16
  constructor(mcpServer: McpServer);
14
- handler({ absoluteProjectPath, }: InputSchemaType): Promise<TextContentResponse>;
17
+ handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, }: InputSchemaType): Promise<TextContentResponse>;
15
18
  register(): RegisteredTool;
16
19
  }
17
20
  export {};
@@ -1,11 +1,12 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { runCommandInDir } from '../../utils/project.js';
3
- import { absoluteProjectPath } from './constants.js';
3
+ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
4
4
  import z from 'zod';
5
5
  import { formatTextContents } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  const inputSchema = {
8
8
  absoluteProjectPath,
9
+ absoluteCurrentWorkingDirectory,
9
10
  };
10
11
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
12
  const inputSchemaZodObject = z.object({
@@ -16,10 +17,10 @@ export class UploadProjectTools extends Tool {
16
17
  constructor(mcpServer) {
17
18
  super(mcpServer);
18
19
  }
19
- async handler({ absoluteProjectPath, }) {
20
+ async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, }) {
20
21
  await trackToolUsage(toolName);
21
22
  const { stdout, stderr } = await runCommandInDir(absoluteProjectPath, `hs project upload --force-create`);
22
- return formatTextContents(stdout, stderr);
23
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
23
24
  }
24
25
  register() {
25
26
  return this.mcpServer.registerTool(toolName, {
@@ -3,15 +3,18 @@ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.
3
3
  import { z } from 'zod';
4
4
  declare const inputSchemaZodObject: z.ZodObject<{
5
5
  absoluteProjectPath: z.ZodString;
6
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
7
  }, "strip", z.ZodTypeAny, {
7
8
  absoluteProjectPath: string;
9
+ absoluteCurrentWorkingDirectory: string;
8
10
  }, {
9
11
  absoluteProjectPath: string;
12
+ absoluteCurrentWorkingDirectory: string;
10
13
  }>;
11
14
  export type CreateProjectInputSchema = z.infer<typeof inputSchemaZodObject>;
12
15
  export declare class ValidateProjectTool extends Tool<CreateProjectInputSchema> {
13
16
  constructor(mcpServer: McpServer);
14
- handler({ absoluteProjectPath, }: CreateProjectInputSchema): Promise<TextContentResponse>;
17
+ handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, }: CreateProjectInputSchema): Promise<TextContentResponse>;
15
18
  register(): RegisteredTool;
16
19
  }
17
20
  export {};
@@ -1,11 +1,12 @@
1
1
  import { Tool } from '../../types.js';
2
2
  import { z } from 'zod';
3
- import { absoluteProjectPath } from './constants.js';
3
+ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
4
4
  import { runCommandInDir } from '../../utils/project.js';
5
5
  import { formatTextContents } from '../../utils/content.js';
6
6
  import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
7
  const inputSchema = {
8
8
  absoluteProjectPath,
9
+ absoluteCurrentWorkingDirectory,
9
10
  };
10
11
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
11
12
  const inputSchemaZodObject = z.object({ ...inputSchema });
@@ -14,14 +15,14 @@ export class ValidateProjectTool extends Tool {
14
15
  constructor(mcpServer) {
15
16
  super(mcpServer);
16
17
  }
17
- async handler({ absoluteProjectPath, }) {
18
+ async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, }) {
18
19
  await trackToolUsage(toolName);
19
20
  try {
20
21
  const { stdout, stderr } = await runCommandInDir(absoluteProjectPath, 'hs project validate');
21
- return formatTextContents(stdout, stderr);
22
+ return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
22
23
  }
23
24
  catch (error) {
24
- return formatTextContents(error instanceof Error ? error.message : `${error}`);
25
+ return formatTextContents(absoluteCurrentWorkingDirectory, error instanceof Error ? error.message : `${error}`);
25
26
  }
26
27
  }
27
28
  register() {
@@ -2,11 +2,14 @@ import { AddFeatureToProjectTool, } from '../AddFeatureToProjectTool.js';
2
2
  import { runCommandInDir } from '../../../utils/project.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
4
  import { APP_AUTH_TYPES, APP_DISTRIBUTION_TYPES, } from '../../../../lib/constants.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('../../../utils/project');
7
8
  vi.mock('../../../utils/command');
8
9
  vi.mock('../../../../lib/constants');
9
10
  vi.mock('../../../utils/toolUsageTracking');
11
+ vi.mock('../../../utils/feedbackTracking');
12
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
10
13
  const mockRunCommandInDir = runCommandInDir;
11
14
  const mockAddFlag = addFlag;
12
15
  describe('mcp-server/tools/project/AddFeatureToProject', () => {
@@ -21,6 +24,7 @@ describe('mcp-server/tools/project/AddFeatureToProject', () => {
21
24
  };
22
25
  mockRegisteredTool = {};
23
26
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
27
+ mockMcpFeedbackRequest.mockResolvedValue('');
24
28
  tool = new AddFeatureToProjectTool(mockMcpServer);
25
29
  // Mock addFlag to simulate command building
26
30
  mockAddFlag.mockImplementation((command, flag, value) => `${command} --${flag} "${value}"`);
@@ -32,12 +36,13 @@ describe('mcp-server/tools/project/AddFeatureToProject', () => {
32
36
  title: 'Add feature to HubSpot Project',
33
37
  description: expect.stringContaining('Adds a feature to an existing HubSpot project'),
34
38
  inputSchema: expect.any(Object),
35
- }), tool.handler);
39
+ }), expect.any(Function));
36
40
  expect(result).toBe(mockRegisteredTool);
37
41
  });
38
42
  });
39
43
  describe('handler', () => {
40
44
  const baseInput = {
45
+ absoluteCurrentWorkingDirectory: '/test/dir',
41
46
  absoluteProjectPath: '/test/project',
42
47
  addApp: false,
43
48
  };
@@ -2,12 +2,15 @@ import { CreateProjectTool, } from '../CreateProjectTool.js';
2
2
  import { runCommandInDir } from '../../../utils/project.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
4
  import { APP_DISTRIBUTION_TYPES, EMPTY_PROJECT, PROJECT_WITH_APP, } from '../../../../lib/constants.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('../../../utils/project');
7
8
  vi.mock('../../../utils/command');
8
9
  vi.mock('../../../../lib/constants');
9
10
  vi.mock('../../../../lib/projects/create/v2');
10
11
  vi.mock('../../../utils/toolUsageTracking');
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
11
14
  const mockRunCommandInDir = runCommandInDir;
12
15
  const mockAddFlag = addFlag;
13
16
  describe('mcp-server/tools/project/CreateProjectTool', () => {
@@ -22,6 +25,7 @@ describe('mcp-server/tools/project/CreateProjectTool', () => {
22
25
  };
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
25
29
  tool = new CreateProjectTool(mockMcpServer);
26
30
  // Mock addFlag to simulate command building
27
31
  mockAddFlag.mockImplementation((command, flag, value) => `${command} --${flag} "${value}"`);
@@ -29,11 +33,11 @@ describe('mcp-server/tools/project/CreateProjectTool', () => {
29
33
  describe('register', () => {
30
34
  it('should register tool with correct parameters', () => {
31
35
  const result = tool.register();
32
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-project', {
36
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-project', expect.objectContaining({
33
37
  title: 'Create HubSpot Project',
34
- description: 'Creates a HubSpot project with the provided name and outputs it in the provided destination',
38
+ description: expect.stringContaining('Creates a HubSpot project with the provided name'),
35
39
  inputSchema: expect.any(Object),
36
- }, tool.handler);
40
+ }), expect.any(Function));
37
41
  expect(result).toBe(mockRegisteredTool);
38
42
  });
39
43
  });
@@ -1,10 +1,13 @@
1
1
  import { DeployProjectTool } from '../DeployProjectTool.js';
2
2
  import { runCommandInDir } from '../../../utils/project.js';
3
3
  import { addFlag } from '../../../utils/command.js';
4
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
4
5
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
5
6
  vi.mock('../../../utils/project');
6
7
  vi.mock('../../../utils/command');
7
8
  vi.mock('../../../utils/toolUsageTracking');
9
+ vi.mock('../../../utils/feedbackTracking');
10
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
8
11
  const mockRunCommandInDir = runCommandInDir;
9
12
  const mockAddFlag = addFlag;
10
13
  describe('mcp-server/tools/project/DeployProject', () => {
@@ -19,6 +22,7 @@ describe('mcp-server/tools/project/DeployProject', () => {
19
22
  };
20
23
  mockRegisteredTool = {};
21
24
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
25
+ mockMcpFeedbackRequest.mockResolvedValue('');
22
26
  tool = new DeployProjectTool(mockMcpServer);
23
27
  // Mock addFlag to simulate command building
24
28
  mockAddFlag.mockImplementation((command, flag, value) => `${command} --${flag} "${value}"`);
@@ -26,16 +30,17 @@ describe('mcp-server/tools/project/DeployProject', () => {
26
30
  describe('register', () => {
27
31
  it('should register tool with correct parameters', () => {
28
32
  const result = tool.register();
29
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('deploy-project', {
33
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('deploy-project', expect.objectContaining({
30
34
  title: 'Deploy a build of HubSpot Project',
31
35
  description: expect.stringContaining('Takes a build number and a project name and deploys that build of the project'),
32
36
  inputSchema: expect.any(Object),
33
- }, tool.handler);
37
+ }), expect.any(Function));
34
38
  expect(result).toBe(mockRegisteredTool);
35
39
  });
36
40
  });
37
41
  describe('handler', () => {
38
42
  const baseInput = {
43
+ absoluteCurrentWorkingDirectory: '/test/dir',
39
44
  absoluteProjectPath: '/test/project',
40
45
  };
41
46
  it('should deploy project with specified build number', async () => {
@@ -1,10 +1,13 @@
1
1
  import { DocFetchTool } from '../DocFetchTool.js';
2
2
  import { http } from '@hubspot/local-dev-lib/http/unauthed';
3
3
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
4
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
4
5
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
5
6
  vi.mock('@hubspot/local-dev-lib/http/unauthed');
6
7
  vi.mock('@hubspot/local-dev-lib/errors/index');
7
8
  vi.mock('../../utils/toolUsageTracking');
9
+ vi.mock('../../../utils/feedbackTracking');
10
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
8
11
  const mockHttp = http;
9
12
  const mockIsHubSpotHttpError = vi.mocked(isHubSpotHttpError);
10
13
  describe('mcp-server/tools/project/DocFetchTool', () => {
@@ -19,21 +22,23 @@ describe('mcp-server/tools/project/DocFetchTool', () => {
19
22
  };
20
23
  mockRegisteredTool = {};
21
24
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
25
+ mockMcpFeedbackRequest.mockResolvedValue('');
22
26
  tool = new DocFetchTool(mockMcpServer);
23
27
  });
24
28
  describe('register', () => {
25
29
  it('should register tool with correct parameters', () => {
26
30
  const result = tool.register();
27
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('fetch-doc', {
31
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('fetch-doc', expect.objectContaining({
28
32
  title: 'Fetch HubSpot Developer Documentation (single file)',
29
- description: 'Always use this immediately after `search-docs` and before creating a plan, writing code, or answering technical questions. This tool retrieves the full, authoritative content of a HubSpot Developer Documentation page from its URL, ensuring responses are accurate, up-to-date, and grounded in the official docs.',
33
+ description: expect.stringContaining('Always use this immediately after `search-docs`'),
30
34
  inputSchema: expect.any(Object),
31
- }, tool.handler);
35
+ }), expect.any(Function));
32
36
  expect(result).toBe(mockRegisteredTool);
33
37
  });
34
38
  });
35
39
  describe('handler', () => {
36
40
  const mockInput = {
41
+ absoluteCurrentWorkingDirectory: '/test/dir',
37
42
  docUrl: 'https://example.com/docs/test-doc',
38
43
  };
39
44
  it('should successfully fetch and return markdown content', async () => {
@@ -2,11 +2,14 @@ import { DocsSearchTool } from '../DocsSearchTool.js';
2
2
  import { http } from '@hubspot/local-dev-lib/http';
3
3
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
4
4
  import { getAccountIdFromCliConfig } from '../../../utils/cliConfig.js';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('@hubspot/local-dev-lib/http');
7
8
  vi.mock('@hubspot/local-dev-lib/errors/index');
8
9
  vi.mock('../../../utils/toolUsageTracking');
9
10
  vi.mock('../../../utils/cliConfig.js');
11
+ vi.mock('../../../utils/feedbackTracking');
12
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
10
13
  const mockHttp = http;
11
14
  const mockGetAccountIdFromCliConfig = getAccountIdFromCliConfig;
12
15
  const mockIsHubSpotHttpError = vi.mocked(isHubSpotHttpError);
@@ -22,16 +25,17 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
22
25
  };
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
25
29
  tool = new DocsSearchTool(mockMcpServer);
26
30
  });
27
31
  describe('register', () => {
28
- it('should register tool with correct parameters', () => {
32
+ it('should register tool with correct parameters and enhanced description', () => {
29
33
  const result = tool.register();
30
34
  expect(mockMcpServer.registerTool).toHaveBeenCalledWith('search-docs', {
31
35
  title: 'Search HubSpot Developer Documentation',
32
36
  description: 'Use this first whenever you need details about HubSpot APIs, SDKs, integrations, or developer platform features. This searches the official HubSpot Developer Documentation and returns the most relevant pages, each with a URL for use in `fetch-doc`. Always follow this with a fetch to get the full, authoritative content before making plans or writing answers.',
33
37
  inputSchema: expect.any(Object),
34
- }, tool.handler);
38
+ }, expect.any(Function));
35
39
  expect(result).toBe(mockRegisteredTool);
36
40
  });
37
41
  });
@@ -3,11 +3,14 @@ import { z } from 'zod';
3
3
  import { getAccountId } from '@hubspot/local-dev-lib/config';
4
4
  import { http } from '@hubspot/local-dev-lib/http';
5
5
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
6
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
6
7
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
7
8
  vi.mock('../../../utils/toolUsageTracking');
8
9
  vi.mock('@hubspot/local-dev-lib/http');
9
10
  vi.mock('@hubspot/local-dev-lib/errors/index');
10
11
  vi.mock('@hubspot/local-dev-lib/config');
12
+ vi.mock('../../../utils/feedbackTracking');
13
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
11
14
  const mockGetAccountId = getAccountId;
12
15
  const mockHttp = http;
13
16
  const mockIsHubSpotHttpError = isHubSpotHttpError;
@@ -23,14 +26,15 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
23
26
  };
24
27
  mockRegisteredTool = {};
25
28
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
29
+ mockMcpFeedbackRequest.mockResolvedValue('');
26
30
  tool = new GetApiUsagePatternsByAppIdTool(mockMcpServer);
27
31
  });
28
32
  describe('register', () => {
29
33
  it('should register tool with correct parameters', () => {
30
34
  const result = tool.register();
31
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-api-usage-patterns-by-app-id', {
35
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-api-usage-patterns-by-app-id', expect.objectContaining({
32
36
  title: 'Get API Usage Patterns by App ID',
33
- description: 'Retrieves detailed API usage pattern analytics for a specific HubSpot application. Requires an appId (string) to identify the target application. Optionally accepts startDate and endDate parameters in YYYY-MM-DD format to filter results within a specific time range. Returns patternSummaries object containing usage statistics including portalPercentage (percentage of portals using this pattern) and numOfPortals (total count of portals) for different usage patterns. This data helps analyze how the application is being used across different HubSpot portals and can inform optimization decisions.',
37
+ description: expect.stringContaining('Retrieves detailed API usage pattern analytics for a specific HubSpot application'),
34
38
  inputSchema: expect.objectContaining({
35
39
  appId: expect.objectContaining({
36
40
  describe: expect.any(Function),
@@ -42,7 +46,7 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
42
46
  optional: expect.any(Function),
43
47
  }),
44
48
  }),
45
- }, tool.handler);
49
+ }), expect.any(Function));
46
50
  expect(result).toBe(mockRegisteredTool);
47
51
  });
48
52
  });
@@ -79,6 +83,7 @@ describe('mcp-server/tools/project/GetApiUsagePatternsByAppIdTool', () => {
79
83
  });
80
84
  describe('handler', () => {
81
85
  const input = {
86
+ absoluteCurrentWorkingDirectory: '/test/dir',
82
87
  appId: '12345',
83
88
  startDate: '2025-01-01',
84
89
  endDate: '2025-12-31',
@@ -2,11 +2,14 @@ import { GetApplicationInfoTool } from '../GetApplicationInfoTool.js';
2
2
  import { getAccountId } from '@hubspot/local-dev-lib/config';
3
3
  import { http } from '@hubspot/local-dev-lib/http';
4
4
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
5
+ import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
5
6
  vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
6
7
  vi.mock('../../utils/toolUsageTracking');
7
8
  vi.mock('@hubspot/local-dev-lib/http');
8
9
  vi.mock('@hubspot/local-dev-lib/errors/index');
9
10
  vi.mock('@hubspot/local-dev-lib/config');
11
+ vi.mock('../../../utils/feedbackTracking');
12
+ const mockMcpFeedbackRequest = mcpFeedbackRequest;
10
13
  const mockGetAccountId = getAccountId;
11
14
  const mockHttp = http;
12
15
  const mockIsHubSpotHttpError = isHubSpotHttpError;
@@ -22,21 +25,22 @@ describe('mcp-server/tools/project/GetApplicationInfoTool', () => {
22
25
  };
23
26
  mockRegisteredTool = {};
24
27
  mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
28
+ mockMcpFeedbackRequest.mockResolvedValue('');
25
29
  tool = new GetApplicationInfoTool(mockMcpServer);
26
30
  });
27
31
  describe('register', () => {
28
32
  it('should register tool with correct parameters', () => {
29
33
  const result = tool.register();
30
- expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-applications-info', {
34
+ expect(mockMcpServer.registerTool).toHaveBeenCalledWith('get-applications-info', expect.objectContaining({
31
35
  title: 'Get Applications Information',
32
- description: 'Retrieves a list of all HubSpot applications available in the current account. Returns an array of applications, where each application contains an appId (numeric identifier) and appName (string). This information is useful for identifying available applications before using other tools that require specific application IDs, such as getting API usage patterns. No input parameters are required - this tool fetches all applications from the HubSpot Insights API.',
33
- inputSchema: {},
34
- }, tool.handler);
36
+ description: expect.stringContaining('Retrieves a list of all HubSpot applications available in the current account'),
37
+ inputSchema: expect.any(Object),
38
+ }), expect.any(Function));
35
39
  expect(result).toBe(mockRegisteredTool);
36
40
  });
37
41
  });
38
42
  describe('handler', () => {
39
- const input = {};
43
+ const input = { absoluteCurrentWorkingDirectory: '/test/dir' };
40
44
  beforeEach(() => {
41
45
  mockGetAccountId.mockReturnValue(123456789);
42
46
  mockIsHubSpotHttpError.mockReturnValue(false);