@hubspot/cli 7.7.30-experimental.0 → 7.7.32-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 (33) hide show
  1. package/lang/en.d.ts +3 -1
  2. package/lang/en.js +4 -2
  3. package/lib/projects/__tests__/AppDevModeInterface.test.js +10 -0
  4. package/lib/projects/localDev/AppDevModeInterface.d.ts +5 -1
  5. package/lib/projects/localDev/AppDevModeInterface.js +33 -10
  6. package/mcp-server/tools/cms/HsCreateFunctionTool.d.ts +32 -0
  7. package/mcp-server/tools/cms/HsCreateFunctionTool.js +96 -0
  8. package/mcp-server/tools/cms/HsCreateTemplateTool.d.ts +26 -0
  9. package/mcp-server/tools/cms/HsCreateTemplateTool.js +75 -0
  10. package/mcp-server/tools/cms/HsListFunctionsTool.d.ts +23 -0
  11. package/mcp-server/tools/cms/HsListFunctionsTool.js +58 -0
  12. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.d.ts +1 -0
  13. package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +251 -0
  14. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.d.ts +1 -0
  15. package/mcp-server/tools/cms/__tests__/HsCreateTemplateTool.test.js +206 -0
  16. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.d.ts +1 -0
  17. package/mcp-server/tools/cms/__tests__/HsListFunctionsTool.test.js +120 -0
  18. package/mcp-server/tools/index.js +6 -0
  19. package/package.json +2 -1
  20. package/ui/components/BoxWithTitle.d.ts +8 -0
  21. package/ui/components/BoxWithTitle.js +9 -0
  22. package/ui/components/HorizontalSelectPrompt.d.ts +8 -0
  23. package/ui/components/HorizontalSelectPrompt.js +30 -0
  24. package/ui/components/StatusMessageBoxes.d.ts +12 -0
  25. package/ui/components/StatusMessageBoxes.js +31 -0
  26. package/ui/lib/ui-testing-utils.d.ts +9 -0
  27. package/ui/lib/ui-testing-utils.js +47 -0
  28. package/ui/lib/useTerminalSize.d.ts +13 -0
  29. package/ui/lib/useTerminalSize.js +31 -0
  30. package/ui/styles.d.ts +18 -0
  31. package/ui/styles.js +18 -0
  32. package/ui/views/UiSandbox.d.ts +5 -0
  33. package/ui/views/UiSandbox.js +25 -0
package/lang/en.d.ts CHANGED
@@ -2569,6 +2569,8 @@ export declare const lib: {
2569
2569
  readonly activeInstallations: (appName: string, installCount: number) => string;
2570
2570
  readonly error: "An error occurred while checking installations for your app";
2571
2571
  };
2572
+ readonly distributionChanged: `Your app's distribution type has been changed from private to marketplace. Once uploaded, this change cannot be reversed. Before uploading your project, confirm that you want to ${string} change your app's distribution type. This will uninstall your app from all accounts.`;
2573
+ readonly authTypeChanged: `Your app's auth type has been changed from static to oauth. Once uploaded, this change cannot be reversed. Before uploading your project, confirm that you want to ${string} change your app's auth type. This will uninstall your app from all accounts.`;
2572
2574
  };
2573
2575
  readonly LocalDevWebsocketServer: {
2574
2576
  readonly errors: {
@@ -3380,7 +3382,7 @@ Run ${string} to upgrade to version ${string}`;
3380
3382
  readonly componentsToBeMigrated: (components: string) => string;
3381
3383
  readonly componentsThatWillNotBeMigrated: (components: string) => string;
3382
3384
  readonly sourceContentsMoved: (newLocation: string) => string;
3383
- readonly projectMigrationWarningTitle: "⚠️ Important: Migrating to platformVersion 2025.2 is irreversible ⚠️";
3385
+ readonly projectMigrationWarningTitle: "Important: Migrating to platformVersion 2025.2 is irreversible";
3384
3386
  readonly projectMigrationWarning: string;
3385
3387
  readonly success: {
3386
3388
  readonly downloadedProject: (projectName: string, projectDest: string) => string;
package/lang/en.js CHANGED
@@ -5,7 +5,7 @@ import { PERSONAL_ACCESS_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constant
5
5
  import { ARCHIVED_HUBSPOT_CONFIG_YAML_FILE_NAME, GLOBAL_CONFIG_PATH, DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME, } from '@hubspot/local-dev-lib/constants/config';
6
6
  import { uiAccountDescription, uiBetaTag, uiCommandReference, uiLink, UI_COLORS, } from '../lib/ui/index.js';
7
7
  import { getProjectDetailUrl, getProjectSettingsUrl, getLocalDevUiUrl, } from '../lib/projects/urls.js';
8
- import { PROJECT_CONFIG_FILE, PROJECT_WITH_APP } from '../lib/constants.js';
8
+ import { APP_DISTRIBUTION_TYPES, APP_AUTH_TYPES, PROJECT_CONFIG_FILE, PROJECT_WITH_APP, } from '../lib/constants.js';
9
9
  import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier';
10
10
  export const commands = {
11
11
  generalErrors: {
@@ -2566,6 +2566,8 @@ export const lib = {
2566
2566
  activeInstallations: (appName, installCount) => `[WARNING] Your app ${chalk.bold(appName)} is installed in ${chalk.bold(`${installCount} ${installCount === 1 ? 'account' : 'accounts'}`)}`,
2567
2567
  error: 'An error occurred while checking installations for your app',
2568
2568
  },
2569
+ distributionChanged: `Your app's distribution type has been changed from ${APP_DISTRIBUTION_TYPES.PRIVATE} to ${APP_DISTRIBUTION_TYPES.MARKETPLACE}. Once uploaded, this change cannot be reversed. Before uploading your project, confirm that you want to ${chalk.bold('permanantly')} change your app's distribution type. This will uninstall your app from all accounts.`,
2570
+ authTypeChanged: `Your app's auth type has been changed from ${APP_AUTH_TYPES.STATIC} to ${APP_AUTH_TYPES.OAUTH}. Once uploaded, this change cannot be reversed. Before uploading your project, confirm that you want to ${chalk.bold('permanantly')} change your app's auth type. This will uninstall your app from all accounts.`,
2569
2571
  },
2570
2572
  LocalDevWebsocketServer: {
2571
2573
  errors: {
@@ -3374,7 +3376,7 @@ export const lib = {
3374
3376
  componentsToBeMigrated: (components) => `The following features will be migrated: ${components}`,
3375
3377
  componentsThatWillNotBeMigrated: (components) => `[NOTE] These features are not yet supported for migration but will be available later: ${components}`,
3376
3378
  sourceContentsMoved: (newLocation) => `The contents of your old source directory have been moved to ${newLocation}, move any required files to the new source directory.`,
3377
- projectMigrationWarningTitle: '⚠️ Important: Migrating to platformVersion 2025.2 is irreversible ⚠️',
3379
+ projectMigrationWarningTitle: 'Important: Migrating to platformVersion 2025.2 is irreversible',
3378
3380
  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),
3379
3381
  success: {
3380
3382
  downloadedProject: (projectName, projectDest) => `Saved ${projectName} to ${projectDest}`,
@@ -100,6 +100,7 @@ describe('AppDevModeInterface', () => {
100
100
  setAppDataForUid: vi.fn(),
101
101
  addListener: vi.fn(),
102
102
  addUploadWarning: vi.fn(),
103
+ removeListener: vi.fn(),
103
104
  };
104
105
  mockLocalDevLogger = {};
105
106
  // Mock constructors
@@ -387,6 +388,15 @@ describe('AppDevModeInterface', () => {
387
388
  await appDevModeInterface.cleanup();
388
389
  expect(UIEDevModeInterface.cleanup).toHaveBeenCalled();
389
390
  });
391
+ it('should remove state listeners', async () => {
392
+ await appDevModeInterface.cleanup();
393
+ expect(mockLocalDevState.removeListener).toHaveBeenCalledWith('devServerMessage',
394
+ // @ts-expect-error access private method for testing
395
+ appDevModeInterface.onDevServerMessage);
396
+ expect(mockLocalDevState.removeListener).toHaveBeenCalledWith('projectNodes',
397
+ // @ts-expect-error
398
+ appDevModeInterface.onChangeProjectNodes);
399
+ });
390
400
  });
391
401
  describe('isAutomaticallyInstallable()', () => {
392
402
  it('should return true for static auth app on test account with correct parent', () => {
@@ -11,6 +11,7 @@ declare class AppDevModeInterface {
11
11
  _appNode?: AppIRNode | null;
12
12
  marketplaceAppInstalls?: number;
13
13
  constructor(options: AppDevModeInterfaceConstructorOptions);
14
+ private getAppNodeFromProjectNodes;
14
15
  private get appNode();
15
16
  private get appData();
16
17
  private set appData(value);
@@ -21,7 +22,10 @@ declare class AppDevModeInterface {
21
22
  private autoInstallStaticAuthApp;
22
23
  private installAppOrOpenInstallUrl;
23
24
  private checkTestAccountAppInstallation;
24
- private setUpLocalDevServerMessageListeners;
25
+ private onDevServerMessage;
26
+ private onChangeProjectNodes;
27
+ private setUpStateListeners;
28
+ private removeStateListeners;
25
29
  setup(args: any): Promise<void>;
26
30
  start(): Promise<void>;
27
31
  fileChange(filePath: string, event: string): Promise<void>;
@@ -30,12 +30,13 @@ class AppDevModeInterface {
30
30
  process.exit(EXIT_CODES.ERROR);
31
31
  }
32
32
  }
33
+ getAppNodeFromProjectNodes(projectNodes) {
34
+ return Object.values(projectNodes).find(isAppIRNode) || null;
35
+ }
33
36
  // Assumes only one app per project
34
37
  get appNode() {
35
38
  if (this._appNode === undefined) {
36
- this._appNode =
37
- Object.values(this.localDevState.projectNodes).find(isAppIRNode) ||
38
- null;
39
+ this._appNode = this.getAppNodeFromProjectNodes(this.localDevState.projectNodes);
39
40
  }
40
41
  return this._appNode;
41
42
  }
@@ -175,12 +176,33 @@ class AppDevModeInterface {
175
176
  }
176
177
  return { needsInstall: !isInstalledWithScopeGroups, isReinstall };
177
178
  }
178
- setUpLocalDevServerMessageListeners() {
179
- this.localDevState.addListener('devServerMessage', message => {
180
- if (message === LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED) {
181
- this.checkTestAccountAppInstallation();
182
- }
183
- });
179
+ onDevServerMessage = (message) => {
180
+ if (message === LOCAL_DEV_SERVER_MESSAGE_TYPES.WEBSOCKET_SERVER_CONNECTED) {
181
+ this.checkTestAccountAppInstallation();
182
+ }
183
+ };
184
+ onChangeProjectNodes = (nodes) => {
185
+ const newAppNode = this.getAppNodeFromProjectNodes(nodes);
186
+ const oldDistribution = this.appNode?.config.distribution;
187
+ const newDistribution = newAppNode?.config.distribution;
188
+ const oldAuthType = this.appNode?.config.auth.type;
189
+ const newAuthType = newAppNode?.config.auth.type;
190
+ if (newDistribution === APP_DISTRIBUTION_TYPES.MARKETPLACE &&
191
+ oldDistribution !== APP_DISTRIBUTION_TYPES.MARKETPLACE) {
192
+ this.localDevState.addUploadWarning(lib.AppDevModeInterface.distributionChanged);
193
+ }
194
+ else if (newAuthType === APP_AUTH_TYPES.OAUTH &&
195
+ oldAuthType !== APP_AUTH_TYPES.OAUTH) {
196
+ this.localDevState.addUploadWarning(lib.AppDevModeInterface.authTypeChanged);
197
+ }
198
+ };
199
+ setUpStateListeners() {
200
+ this.localDevState.addListener('devServerMessage', this.onDevServerMessage);
201
+ this.localDevState.addListener('projectNodes', this.onChangeProjectNodes);
202
+ }
203
+ removeStateListeners() {
204
+ this.localDevState.removeListener('devServerMessage', this.onDevServerMessage);
205
+ this.localDevState.removeListener('projectNodes', this.onChangeProjectNodes);
184
206
  }
185
207
  // @ts-expect-error TODO: reconcile types between CLI and UIE Dev Server
186
208
  // In the future, update UIE Dev Server to use LocalDevState
@@ -215,7 +237,7 @@ class AppDevModeInterface {
215
237
  catch (e) {
216
238
  logError(e);
217
239
  }
218
- this.setUpLocalDevServerMessageListeners();
240
+ this.setUpStateListeners();
219
241
  return UIEDevModeInterface.setup(args);
220
242
  }
221
243
  async start() {
@@ -239,6 +261,7 @@ class AppDevModeInterface {
239
261
  if (!this.appNode) {
240
262
  return;
241
263
  }
264
+ this.removeStateListeners();
242
265
  return UIEDevModeInterface.cleanup();
243
266
  }
244
267
  }
@@ -0,0 +1,32 @@
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
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ dest: z.ZodOptional<z.ZodString>;
7
+ functionsFolder: z.ZodOptional<z.ZodString>;
8
+ filename: z.ZodOptional<z.ZodString>;
9
+ endpointMethod: z.ZodOptional<z.ZodEnum<["DELETE", "GET", "PATCH", "POST", "PUT"]>>;
10
+ endpointPath: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ absoluteCurrentWorkingDirectory: string;
13
+ dest?: string | undefined;
14
+ functionsFolder?: string | undefined;
15
+ filename?: string | undefined;
16
+ endpointMethod?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" | undefined;
17
+ endpointPath?: string | undefined;
18
+ }, {
19
+ absoluteCurrentWorkingDirectory: string;
20
+ dest?: string | undefined;
21
+ functionsFolder?: string | undefined;
22
+ filename?: string | undefined;
23
+ endpointMethod?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" | undefined;
24
+ endpointPath?: string | undefined;
25
+ }>;
26
+ export type HsCreateFunctionInputSchema = z.infer<typeof inputSchemaZodObject>;
27
+ export declare class HsCreateFunctionTool extends Tool<HsCreateFunctionInputSchema> {
28
+ constructor(mcpServer: McpServer);
29
+ handler({ dest, functionsFolder, filename, endpointMethod, endpointPath, absoluteCurrentWorkingDirectory, }: HsCreateFunctionInputSchema): Promise<TextContentResponse>;
30
+ register(): RegisteredTool;
31
+ }
32
+ export {};
@@ -0,0 +1,96 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
+ import { runCommandInDir } from '../../utils/project.js';
5
+ import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
+ import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
+ import { addFlag } from '../../utils/command.js';
8
+ import { HTTP_METHODS } from '../../../types/Cms.js';
9
+ const inputSchema = {
10
+ absoluteCurrentWorkingDirectory,
11
+ dest: z
12
+ .string()
13
+ .describe('The destination path where the function should be created on the current computer.')
14
+ .optional(),
15
+ functionsFolder: z
16
+ .string()
17
+ .describe('Folder name for function creation. Required for non-interactive function creation. If the user has not specified the folder name, ask them to provide it.')
18
+ .optional(),
19
+ filename: z
20
+ .string()
21
+ .describe('Function filename. Required for non-interactive function creation. If the user has not specified the filename, ask them to provide it.')
22
+ .optional(),
23
+ endpointMethod: z
24
+ .enum(HTTP_METHODS)
25
+ .describe(`HTTP method for the function endpoint. Must be one of: ${HTTP_METHODS.join(', ')}. Defaults to GET.`)
26
+ .optional(),
27
+ endpointPath: z
28
+ .string()
29
+ .describe('API endpoint path for the function. Required for non-interactive function creation. If the user has not specified the endpoint path, ask them to provide it.')
30
+ .optional(),
31
+ };
32
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
+ const inputSchemaZodObject = z.object({ ...inputSchema });
34
+ const toolName = 'create-hubspot-cms-function';
35
+ export class HsCreateFunctionTool extends Tool {
36
+ constructor(mcpServer) {
37
+ super(mcpServer);
38
+ }
39
+ async handler({ dest, functionsFolder, filename, endpointMethod, endpointPath, absoluteCurrentWorkingDirectory, }) {
40
+ await trackToolUsage(toolName);
41
+ const content = [];
42
+ // Require functions folder
43
+ if (!functionsFolder) {
44
+ content.push(formatTextContent(`Ask the user to provide the folder name for the function.`));
45
+ }
46
+ // Require filename
47
+ if (!filename) {
48
+ content.push(formatTextContent(`Ask the user to provide the filename for the function.`));
49
+ }
50
+ // Require endpoint path
51
+ if (!endpointPath) {
52
+ content.push(formatTextContent(`Ask the user to provide the API endpoint path for the function.`));
53
+ }
54
+ // If we have missing required information, return the prompts
55
+ if (content.length > 0) {
56
+ return {
57
+ content,
58
+ };
59
+ }
60
+ // Build the command
61
+ let command = 'hs create function';
62
+ if (dest) {
63
+ command += ` "${dest}"`;
64
+ }
65
+ // Add function-specific flags
66
+ if (functionsFolder) {
67
+ command = addFlag(command, 'functions-folder', functionsFolder);
68
+ }
69
+ if (filename) {
70
+ command = addFlag(command, 'filename', filename);
71
+ }
72
+ if (endpointMethod) {
73
+ command = addFlag(command, 'endpoint-method', endpointMethod);
74
+ }
75
+ else {
76
+ command = addFlag(command, 'endpoint-method', 'GET');
77
+ }
78
+ if (endpointPath) {
79
+ command = addFlag(command, 'endpoint-path', endpointPath);
80
+ }
81
+ try {
82
+ const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
83
+ return formatTextContents(stdout, stderr);
84
+ }
85
+ catch (error) {
86
+ return formatTextContents(error instanceof Error ? error.message : `${error}`);
87
+ }
88
+ }
89
+ register() {
90
+ return this.mcpServer.registerTool(toolName, {
91
+ title: 'Create HubSpot CMS Serverless Function',
92
+ description: `Creates a new HubSpot CMS serverless function using the hs create function command. Functions can be created non-interactively by specifying functionsFolder, filename, and endpointPath. Supports all HTTP methods (${HTTP_METHODS.join(', ')}).`,
93
+ inputSchema,
94
+ }, this.handler);
95
+ }
96
+ }
@@ -0,0 +1,26 @@
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
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ userSuppliedName: z.ZodOptional<z.ZodString>;
7
+ dest: z.ZodOptional<z.ZodString>;
8
+ templateType: z.ZodOptional<z.ZodEnum<["page-template", "email-template", "partial", "global-partial", "blog-listing-template", "blog-post-template", "search-template", "section"]>>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ absoluteCurrentWorkingDirectory: string;
11
+ dest?: string | undefined;
12
+ templateType?: "page-template" | "email-template" | "partial" | "global-partial" | "blog-listing-template" | "blog-post-template" | "search-template" | "section" | undefined;
13
+ userSuppliedName?: string | undefined;
14
+ }, {
15
+ absoluteCurrentWorkingDirectory: string;
16
+ dest?: string | undefined;
17
+ templateType?: "page-template" | "email-template" | "partial" | "global-partial" | "blog-listing-template" | "blog-post-template" | "search-template" | "section" | undefined;
18
+ userSuppliedName?: string | undefined;
19
+ }>;
20
+ export type HsCreateTemplateInputSchema = z.infer<typeof inputSchemaZodObject>;
21
+ export declare class HsCreateTemplateTool extends Tool<HsCreateTemplateInputSchema> {
22
+ constructor(mcpServer: McpServer);
23
+ handler({ userSuppliedName, dest, templateType, absoluteCurrentWorkingDirectory, }: HsCreateTemplateInputSchema): Promise<TextContentResponse>;
24
+ register(): RegisteredTool;
25
+ }
26
+ export {};
@@ -0,0 +1,75 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
4
+ import { runCommandInDir } from '../../utils/project.js';
5
+ import { formatTextContents, formatTextContent } from '../../utils/content.js';
6
+ import { trackToolUsage } from '../../utils/toolUsageTracking.js';
7
+ import { addFlag } from '../../utils/command.js';
8
+ import { TEMPLATE_TYPES } from '../../../types/Cms.js';
9
+ const inputSchema = {
10
+ absoluteCurrentWorkingDirectory,
11
+ userSuppliedName: z
12
+ .string()
13
+ .describe('REQUIRED - If not specified by the user, DO NOT choose. Ask the user to specify the name of the template they want to create.')
14
+ .optional(),
15
+ dest: z
16
+ .string()
17
+ .describe('The destination path where the template should be created on the current computer.')
18
+ .optional(),
19
+ templateType: z
20
+ .enum(TEMPLATE_TYPES)
21
+ .describe(`Template type for template creation. Must be one of: ${TEMPLATE_TYPES.join(', ')}`)
22
+ .optional(),
23
+ };
24
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
25
+ const inputSchemaZodObject = z.object({ ...inputSchema });
26
+ const toolName = 'create-hubspot-cms-template';
27
+ export class HsCreateTemplateTool extends Tool {
28
+ constructor(mcpServer) {
29
+ super(mcpServer);
30
+ }
31
+ async handler({ userSuppliedName, dest, templateType, absoluteCurrentWorkingDirectory, }) {
32
+ await trackToolUsage(toolName);
33
+ const content = [];
34
+ // Always require a name
35
+ if (!userSuppliedName) {
36
+ content.push(formatTextContent(`Ask the user to specify the name of the template they want to create.`));
37
+ }
38
+ // Require template type
39
+ if (!templateType) {
40
+ content.push(formatTextContent(`Ask the user what template type they want to create. Options are: ${TEMPLATE_TYPES.join(', ')}`));
41
+ }
42
+ // If we have missing required information, return the prompts
43
+ if (content.length > 0) {
44
+ return {
45
+ content,
46
+ };
47
+ }
48
+ // Build the command
49
+ let command = 'hs create template';
50
+ if (userSuppliedName) {
51
+ command += ` "${userSuppliedName}"`;
52
+ }
53
+ if (dest) {
54
+ command += ` "${dest}"`;
55
+ }
56
+ // Add template type flag
57
+ if (templateType) {
58
+ command = addFlag(command, 'template-type', templateType);
59
+ }
60
+ try {
61
+ const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
62
+ return formatTextContents(stdout, stderr);
63
+ }
64
+ catch (error) {
65
+ return formatTextContents(error instanceof Error ? error.message : `${error}`);
66
+ }
67
+ }
68
+ register() {
69
+ return this.mcpServer.registerTool(toolName, {
70
+ title: 'Create HubSpot CMS Template',
71
+ description: `Creates a new HubSpot CMS template using the hs create template command. Templates can be created non-interactively by specifying templateType. Supports all template types including: ${TEMPLATE_TYPES.join(', ')}.`,
72
+ inputSchema,
73
+ }, this.handler);
74
+ }
75
+ }
@@ -0,0 +1,23 @@
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
+ absoluteCurrentWorkingDirectory: z.ZodString;
6
+ account: z.ZodOptional<z.ZodString>;
7
+ json: z.ZodOptional<z.ZodBoolean>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ absoluteCurrentWorkingDirectory: string;
10
+ account?: string | undefined;
11
+ json?: boolean | undefined;
12
+ }, {
13
+ absoluteCurrentWorkingDirectory: string;
14
+ account?: string | undefined;
15
+ json?: boolean | undefined;
16
+ }>;
17
+ export type HsListFunctionsInputSchema = z.infer<typeof inputSchemaZodObject>;
18
+ export declare class HsListFunctionsTool extends Tool<HsListFunctionsInputSchema> {
19
+ constructor(mcpServer: McpServer);
20
+ handler({ account, json, absoluteCurrentWorkingDirectory, }: HsListFunctionsInputSchema): Promise<TextContentResponse>;
21
+ register(): RegisteredTool;
22
+ }
23
+ export {};
@@ -0,0 +1,58 @@
1
+ import { Tool } from '../../types.js';
2
+ import { z } from 'zod';
3
+ import { addFlag } from '../../utils/command.js';
4
+ import { absoluteCurrentWorkingDirectory } from '../project/constants.js';
5
+ import { runCommandInDir } from '../../utils/project.js';
6
+ import { formatTextContents } from '../../utils/content.js';
7
+ import { trackToolUsage } from '../../utils/toolUsageTracking.js';
8
+ const inputSchema = {
9
+ absoluteCurrentWorkingDirectory,
10
+ account: z
11
+ .string()
12
+ .describe('The HubSpot account id or name from the HubSpot config file to use for the operation.')
13
+ .optional(),
14
+ json: z
15
+ .boolean()
16
+ .describe('Return raw JSON output instead of formatted table. Useful for programmatic access.')
17
+ .optional(),
18
+ };
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ const inputSchemaZodObject = z.object({ ...inputSchema });
21
+ const toolName = 'list-hubspot-cms-serverless-functions';
22
+ export class HsListFunctionsTool extends Tool {
23
+ constructor(mcpServer) {
24
+ super(mcpServer);
25
+ }
26
+ async handler({ account, json, absoluteCurrentWorkingDirectory, }) {
27
+ await trackToolUsage(toolName);
28
+ let command = 'hs function list';
29
+ if (json) {
30
+ command += ' --json';
31
+ }
32
+ if (account) {
33
+ command = addFlag(command, 'account', account);
34
+ }
35
+ try {
36
+ const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
37
+ return formatTextContents(stdout, stderr);
38
+ }
39
+ catch (error) {
40
+ const errorMessage = error instanceof Error ? error.message : String(error);
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text',
45
+ text: `Error executing hs function list command: ${errorMessage}`,
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ }
51
+ register() {
52
+ return this.mcpServer.registerTool(toolName, {
53
+ title: 'List HubSpot CMS Serverless Functions',
54
+ description: 'Get a list of all serverless functions deployed in a HubSpot portal/account. Shows function routes, HTTP methods, secrets, and timestamps.',
55
+ inputSchema,
56
+ }, this.handler);
57
+ }
58
+ }