@hubspot/cli 7.10.0-beta.0 → 7.10.0-beta.1
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.
- package/commands/account/__tests__/rename.test.js +35 -0
- package/commands/account/rename.d.ts +1 -1
- package/commands/account/rename.js +5 -2
- package/commands/config/set.js +1 -2
- package/commands/getStarted.js +8 -2
- package/commands/hubdb.d.ts +1 -1
- package/commands/project/dev/index.js +8 -1
- package/commands/project/listBuilds.js +7 -1
- package/commands/project/upload.js +7 -1
- package/commands/project/validate.js +7 -1
- package/commands/project/watch.js +7 -2
- package/commands/testAccount/__tests__/create.test.js +68 -0
- package/commands/testAccount/create.d.ts +8 -0
- package/commands/testAccount/create.js +133 -43
- package/commands/testAccount/importData.d.ts +1 -1
- package/lang/en.d.ts +3199 -3204
- package/lang/en.js +24 -3
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +6 -0
- package/lib/mcp/__tests__/setup.test.d.ts +1 -0
- package/lib/mcp/__tests__/setup.test.js +127 -0
- package/lib/mcp/setup.d.ts +4 -12
- package/lib/mcp/setup.js +34 -1
- package/lib/middleware/autoUpdateMiddleware.d.ts +3 -1
- package/lib/middleware/autoUpdateMiddleware.js +1 -0
- package/lib/projects/__tests__/components.test.js +148 -24
- package/lib/projects/__tests__/projects.test.js +13 -42
- package/lib/projects/components.js +76 -20
- package/lib/projects/config.js +5 -9
- package/lib/prompts/__tests__/createDeveloperTestAccountConfigPrompt.test.d.ts +1 -0
- package/lib/prompts/__tests__/createDeveloperTestAccountConfigPrompt.test.js +153 -0
- package/lib/prompts/createDeveloperTestAccountConfigPrompt.d.ts +5 -0
- package/lib/prompts/createDeveloperTestAccountConfigPrompt.js +76 -66
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +6 -0
- package/mcp-server/tools/cms/HsCreateModuleTool.d.ts +4 -4
- package/mcp-server/tools/cms/HsCreateModuleTool.js +6 -0
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +6 -0
- package/mcp-server/tools/cms/HsFunctionLogsTool.d.ts +4 -4
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +4 -0
- package/mcp-server/tools/cms/HsListFunctionsTool.js +4 -0
- package/mcp-server/tools/cms/HsListTool.js +4 -0
- package/mcp-server/tools/index.js +2 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +6 -0
- package/mcp-server/tools/project/CreateProjectTool.js +6 -0
- package/mcp-server/tools/project/CreateTestAccountTool.d.ts +41 -0
- package/mcp-server/tools/project/CreateTestAccountTool.js +137 -0
- package/mcp-server/tools/project/DeployProjectTool.js +6 -0
- package/mcp-server/tools/project/DocFetchTool.js +4 -0
- package/mcp-server/tools/project/DocsSearchTool.js +4 -0
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +4 -0
- package/mcp-server/tools/project/GetApplicationInfoTool.js +4 -0
- package/mcp-server/tools/project/GetConfigValuesTool.js +4 -0
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +4 -0
- package/mcp-server/tools/project/UploadProjectTools.js +6 -0
- package/mcp-server/tools/project/ValidateProjectTool.js +4 -0
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +231 -0
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +2 -2
- package/package.json +1 -1
|
@@ -70,6 +70,12 @@ export class HsCreateTemplateTool extends Tool {
|
|
|
70
70
|
title: 'Create HubSpot CMS Template',
|
|
71
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
72
|
inputSchema,
|
|
73
|
+
annotations: {
|
|
74
|
+
readOnlyHint: false,
|
|
75
|
+
destructiveHint: false,
|
|
76
|
+
idempotentHint: false,
|
|
77
|
+
openWorldHint: false,
|
|
78
|
+
},
|
|
73
79
|
}, this.handler);
|
|
74
80
|
}
|
|
75
81
|
}
|
|
@@ -12,16 +12,16 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
12
12
|
endpoint: string;
|
|
13
13
|
absoluteCurrentWorkingDirectory: string;
|
|
14
14
|
account?: string | undefined;
|
|
15
|
-
limit?: number | undefined;
|
|
16
|
-
compact?: boolean | undefined;
|
|
17
15
|
latest?: boolean | undefined;
|
|
16
|
+
compact?: boolean | undefined;
|
|
17
|
+
limit?: number | undefined;
|
|
18
18
|
}, {
|
|
19
19
|
endpoint: string;
|
|
20
20
|
absoluteCurrentWorkingDirectory: string;
|
|
21
21
|
account?: string | undefined;
|
|
22
|
-
limit?: number | undefined;
|
|
23
|
-
compact?: boolean | undefined;
|
|
24
22
|
latest?: boolean | undefined;
|
|
23
|
+
compact?: boolean | undefined;
|
|
24
|
+
limit?: number | undefined;
|
|
25
25
|
}>;
|
|
26
26
|
export type HsFunctionLogsInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
27
27
|
export declare class HsFunctionLogsTool extends Tool<HsFunctionLogsInputSchema> {
|
|
@@ -64,6 +64,10 @@ export class HsFunctionLogsTool extends Tool {
|
|
|
64
64
|
title: 'Get HubSpot CMS serverless function logs for an endpoint',
|
|
65
65
|
description: 'Retrieve logs for HubSpot CMS serverless functions. Use this tool to help debug issues with serverless functions by reading the production logs. Supports various options like latest, compact, and limiting results. Use after listing functions with list-cms-serverless-functions to get the endpoint path.',
|
|
66
66
|
inputSchema,
|
|
67
|
+
annotations: {
|
|
68
|
+
readOnlyHint: true,
|
|
69
|
+
openWorldHint: true,
|
|
70
|
+
},
|
|
67
71
|
}, this.handler);
|
|
68
72
|
}
|
|
69
73
|
}
|
|
@@ -53,6 +53,10 @@ export class HsListFunctionsTool extends Tool {
|
|
|
53
53
|
title: 'List HubSpot CMS Serverless Functions',
|
|
54
54
|
description: 'Get a list of all serverless functions deployed in a HubSpot portal/account. Shows function routes, HTTP methods, secrets, and timestamps.',
|
|
55
55
|
inputSchema,
|
|
56
|
+
annotations: {
|
|
57
|
+
readOnlyHint: true,
|
|
58
|
+
openWorldHint: true,
|
|
59
|
+
},
|
|
56
60
|
}, this.handler);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -53,6 +53,10 @@ export class HsListTool extends Tool {
|
|
|
53
53
|
title: 'List HubSpot CMS Directory Contents',
|
|
54
54
|
description: 'List remote contents of a HubSpot CMS directory.',
|
|
55
55
|
inputSchema,
|
|
56
|
+
annotations: {
|
|
57
|
+
readOnlyHint: true,
|
|
58
|
+
openWorldHint: true,
|
|
59
|
+
},
|
|
56
60
|
}, this.handler);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -15,11 +15,13 @@ import { HsCreateTemplateTool } from './cms/HsCreateTemplateTool.js';
|
|
|
15
15
|
import { HsCreateFunctionTool } from './cms/HsCreateFunctionTool.js';
|
|
16
16
|
import { HsListFunctionsTool } from './cms/HsListFunctionsTool.js';
|
|
17
17
|
import { HsFunctionLogsTool } from './cms/HsFunctionLogsTool.js';
|
|
18
|
+
import { CreateTestAccountTool } from './project/CreateTestAccountTool.js';
|
|
18
19
|
export function registerProjectTools(mcpServer) {
|
|
19
20
|
return [
|
|
20
21
|
new UploadProjectTools(mcpServer).register(),
|
|
21
22
|
new CreateProjectTool(mcpServer).register(),
|
|
22
23
|
new GuidedWalkthroughTool(mcpServer).register(),
|
|
24
|
+
new CreateTestAccountTool(mcpServer).register(),
|
|
23
25
|
new DeployProjectTool(mcpServer).register(),
|
|
24
26
|
new AddFeatureToProjectTool(mcpServer).register(),
|
|
25
27
|
new ValidateProjectTool(mcpServer).register(),
|
|
@@ -72,6 +72,12 @@ export class AddFeatureToProjectTool extends Tool {
|
|
|
72
72
|
description: `Adds a feature to an existing HubSpot project.
|
|
73
73
|
Only works for projects with platformVersion '2025.2' and beyond`,
|
|
74
74
|
inputSchema,
|
|
75
|
+
annotations: {
|
|
76
|
+
readOnlyHint: false,
|
|
77
|
+
destructiveHint: false,
|
|
78
|
+
idempotentHint: false,
|
|
79
|
+
openWorldHint: false,
|
|
80
|
+
},
|
|
75
81
|
}, this.handler);
|
|
76
82
|
}
|
|
77
83
|
}
|
|
@@ -88,6 +88,12 @@ export class CreateProjectTool extends Tool {
|
|
|
88
88
|
title: 'Create HubSpot Project',
|
|
89
89
|
description: 'Creates a HubSpot project with the provided name and outputs it in the provided destination',
|
|
90
90
|
inputSchema,
|
|
91
|
+
annotations: {
|
|
92
|
+
readOnlyHint: false,
|
|
93
|
+
destructiveHint: false,
|
|
94
|
+
idempotentHint: false,
|
|
95
|
+
openWorldHint: false,
|
|
96
|
+
},
|
|
91
97
|
}, this.handler);
|
|
92
98
|
}
|
|
93
99
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TextContentResponse, Tool } from '../../types.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
declare const createTestAccountInputSchema: z.ZodObject<{
|
|
5
|
+
absoluteCurrentWorkingDirectory: z.ZodString;
|
|
6
|
+
configPath: z.ZodOptional<z.ZodString>;
|
|
7
|
+
name: z.ZodOptional<z.ZodString>;
|
|
8
|
+
description: z.ZodOptional<z.ZodString>;
|
|
9
|
+
marketingLevel: z.ZodOptional<z.ZodEnum<["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"]>>;
|
|
10
|
+
opsLevel: z.ZodOptional<z.ZodEnum<["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"]>>;
|
|
11
|
+
serviceLevel: z.ZodOptional<z.ZodEnum<["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"]>>;
|
|
12
|
+
salesLevel: z.ZodOptional<z.ZodEnum<["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"]>>;
|
|
13
|
+
contentLevel: z.ZodOptional<z.ZodEnum<["FREE", "STARTER", "PROFESSIONAL", "ENTERPRISE"]>>;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
absoluteCurrentWorkingDirectory: string;
|
|
16
|
+
name?: string | undefined;
|
|
17
|
+
description?: string | undefined;
|
|
18
|
+
marketingLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
19
|
+
opsLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
20
|
+
serviceLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
21
|
+
salesLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
22
|
+
contentLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
23
|
+
configPath?: string | undefined;
|
|
24
|
+
}, {
|
|
25
|
+
absoluteCurrentWorkingDirectory: string;
|
|
26
|
+
name?: string | undefined;
|
|
27
|
+
description?: string | undefined;
|
|
28
|
+
marketingLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
29
|
+
opsLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
30
|
+
serviceLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
31
|
+
salesLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
32
|
+
contentLevel?: "FREE" | "STARTER" | "PROFESSIONAL" | "ENTERPRISE" | undefined;
|
|
33
|
+
configPath?: string | undefined;
|
|
34
|
+
}>;
|
|
35
|
+
export type CreateTestAccountInputSchema = z.infer<typeof createTestAccountInputSchema>;
|
|
36
|
+
export declare class CreateTestAccountTool extends Tool<CreateTestAccountInputSchema> {
|
|
37
|
+
constructor(mcpServer: McpServer);
|
|
38
|
+
handler({ absoluteCurrentWorkingDirectory, name, description, marketingLevel, opsLevel, serviceLevel, salesLevel, contentLevel, configPath, }: CreateTestAccountInputSchema): Promise<TextContentResponse>;
|
|
39
|
+
register(): RegisteredTool;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Tool } from '../../types.js';
|
|
2
|
+
import { absoluteCurrentWorkingDirectory } from './constants.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
5
|
+
import { formatTextContents, formatTextContent } from '../../utils/content.js';
|
|
6
|
+
import { addFlag } from '../../utils/command.js';
|
|
7
|
+
import { runCommandInDir } from '../../utils/project.js';
|
|
8
|
+
import { ACCOUNT_LEVEL_CHOICES } from '../../../lib/constants.js';
|
|
9
|
+
const inputSchema = {
|
|
10
|
+
absoluteCurrentWorkingDirectory,
|
|
11
|
+
configPath: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('Path to a test account configuration JSON file. Mutually exclusive with all other parameters.\n\n' +
|
|
15
|
+
'Config file format:\n' +
|
|
16
|
+
'{\n' +
|
|
17
|
+
' "accountName": "AllHubsProfessional",\n' +
|
|
18
|
+
' "description": "Professional test account",\n' +
|
|
19
|
+
' "marketingLevel": "PROFESSIONAL",\n' +
|
|
20
|
+
' "opsLevel": "PROFESSIONAL",\n' +
|
|
21
|
+
' "serviceLevel": "PROFESSIONAL",\n' +
|
|
22
|
+
' "salesLevel": "PROFESSIONAL",\n' +
|
|
23
|
+
' "contentLevel": "PROFESSIONAL"\n' +
|
|
24
|
+
'}'),
|
|
25
|
+
name: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Name for the test account. Required when not using configPath.'),
|
|
29
|
+
description: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Description for the test account. Optional.'),
|
|
33
|
+
marketingLevel: z
|
|
34
|
+
.enum(ACCOUNT_LEVEL_CHOICES)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe(`Marketing Hub tier level. Options: ${ACCOUNT_LEVEL_CHOICES.join(', ')}. Defaults to ENTERPRISE if not specified.`),
|
|
37
|
+
opsLevel: z
|
|
38
|
+
.enum(ACCOUNT_LEVEL_CHOICES)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe(`Operations Hub tier level. Options: ${ACCOUNT_LEVEL_CHOICES.join(', ')}. Defaults to ENTERPRISE if not specified.`),
|
|
41
|
+
serviceLevel: z
|
|
42
|
+
.enum(ACCOUNT_LEVEL_CHOICES)
|
|
43
|
+
.optional()
|
|
44
|
+
.describe(`Service Hub tier level. Options: ${ACCOUNT_LEVEL_CHOICES.join(', ')}. Defaults to ENTERPRISE if not specified.`),
|
|
45
|
+
salesLevel: z
|
|
46
|
+
.enum(ACCOUNT_LEVEL_CHOICES)
|
|
47
|
+
.optional()
|
|
48
|
+
.describe(`Sales Hub tier level. Options: ${ACCOUNT_LEVEL_CHOICES.join(', ')}. Defaults to ENTERPRISE if not specified.`),
|
|
49
|
+
contentLevel: z
|
|
50
|
+
.enum(ACCOUNT_LEVEL_CHOICES)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe(`CMS Hub tier level. Options: ${ACCOUNT_LEVEL_CHOICES.join(', ')}. Defaults to ENTERPRISE if not specified.`),
|
|
53
|
+
};
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
55
|
+
const createTestAccountInputSchema = z.object({ ...inputSchema });
|
|
56
|
+
const toolName = 'create-test-account';
|
|
57
|
+
export class CreateTestAccountTool extends Tool {
|
|
58
|
+
constructor(mcpServer) {
|
|
59
|
+
super(mcpServer);
|
|
60
|
+
}
|
|
61
|
+
async handler({ absoluteCurrentWorkingDirectory, name, description, marketingLevel, opsLevel, serviceLevel, salesLevel, contentLevel, configPath, }) {
|
|
62
|
+
await trackToolUsage(toolName);
|
|
63
|
+
let command = 'hs test-account create';
|
|
64
|
+
const content = [];
|
|
65
|
+
// Use config file if provided (LLM should check for config first)
|
|
66
|
+
if (configPath) {
|
|
67
|
+
command = addFlag(command, 'config-path', configPath);
|
|
68
|
+
}
|
|
69
|
+
// Use flags if name is provided (when no config used)
|
|
70
|
+
else if (name) {
|
|
71
|
+
command = addFlag(command, 'name', name);
|
|
72
|
+
if (description) {
|
|
73
|
+
command = addFlag(command, 'description', description);
|
|
74
|
+
}
|
|
75
|
+
if (marketingLevel) {
|
|
76
|
+
command = addFlag(command, 'marketing-level', marketingLevel);
|
|
77
|
+
}
|
|
78
|
+
if (opsLevel) {
|
|
79
|
+
command = addFlag(command, 'ops-level', opsLevel);
|
|
80
|
+
}
|
|
81
|
+
if (serviceLevel) {
|
|
82
|
+
command = addFlag(command, 'service-level', serviceLevel);
|
|
83
|
+
}
|
|
84
|
+
if (salesLevel) {
|
|
85
|
+
command = addFlag(command, 'sales-level', salesLevel);
|
|
86
|
+
}
|
|
87
|
+
if (contentLevel) {
|
|
88
|
+
command = addFlag(command, 'content-level', contentLevel);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
content.push(formatTextContent(`Ask the user for the account config JSON path or the name of the test account to create.`));
|
|
93
|
+
}
|
|
94
|
+
if (content.length > 0) {
|
|
95
|
+
return {
|
|
96
|
+
content,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// No flags or config - command will prompt user interactively
|
|
100
|
+
try {
|
|
101
|
+
const { stdout, stderr } = await runCommandInDir(absoluteCurrentWorkingDirectory, command);
|
|
102
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, stdout, stderr);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, error instanceof Error ? error.message : `${error}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
register() {
|
|
109
|
+
return this.mcpServer.registerTool(toolName, {
|
|
110
|
+
title: 'Create HubSpot Test Account',
|
|
111
|
+
description: 'Creates a HubSpot developer test account. Test accounts are temporary HubSpot portals used for local development, testing apps, and QA workflows.\n\n' +
|
|
112
|
+
'WORKFLOW:\n' +
|
|
113
|
+
'1. Check the current working directory for existing test account config files (e.g., test-account.json, test-portal-config.json)\n' +
|
|
114
|
+
'2. If config file found:\n' +
|
|
115
|
+
' - Show the user what file(s) you found\n' +
|
|
116
|
+
' - Ask if they want to use the existing config\n' +
|
|
117
|
+
' - If YES: Use configPath parameter only\n' +
|
|
118
|
+
' - If NO: Proceed to step 3\n' +
|
|
119
|
+
'3. If no config file OR user declined:\n' +
|
|
120
|
+
' - Ask the user for ALL account details:\n' +
|
|
121
|
+
' * Account name (required)\n' +
|
|
122
|
+
' * Description (required)\n' +
|
|
123
|
+
' * Hub tier levels for each hub (optional, default to ENTERPRISE if not specified)\n' +
|
|
124
|
+
' - Call this tool with name, description, and all tier level parameters\n' +
|
|
125
|
+
' - IMPORTANT: Always provide all parameters to ensure non-interactive execution\n\n' +
|
|
126
|
+
'Available Hub Tier Levels: FREE, STARTER, PROFESSIONAL, ENTERPRISE\n' +
|
|
127
|
+
'Available Hubs: Marketing (marketingLevel), Sales (salesLevel), Service (serviceLevel), Operations (opsLevel), CMS (contentLevel)',
|
|
128
|
+
inputSchema,
|
|
129
|
+
annotations: {
|
|
130
|
+
readOnlyHint: false,
|
|
131
|
+
destructiveHint: false,
|
|
132
|
+
idempotentHint: false,
|
|
133
|
+
openWorldHint: true,
|
|
134
|
+
},
|
|
135
|
+
}, this.handler);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -45,6 +45,12 @@ export class DeployProjectTool extends Tool {
|
|
|
45
45
|
title: 'Deploy a build of HubSpot Project',
|
|
46
46
|
description: 'Takes a build number and a project name and deploys that build of the project. DO NOT run this tool unless the user specifies they would like to deploy the project.',
|
|
47
47
|
inputSchema,
|
|
48
|
+
annotations: {
|
|
49
|
+
readOnlyHint: false,
|
|
50
|
+
destructiveHint: true,
|
|
51
|
+
idempotentHint: true,
|
|
52
|
+
openWorldHint: true,
|
|
53
|
+
},
|
|
48
54
|
}, this.handler);
|
|
49
55
|
}
|
|
50
56
|
}
|
|
@@ -45,6 +45,10 @@ export class DocFetchTool extends Tool {
|
|
|
45
45
|
title: 'Fetch HubSpot Developer Documentation (single file)',
|
|
46
46
|
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.',
|
|
47
47
|
inputSchema,
|
|
48
|
+
annotations: {
|
|
49
|
+
readOnlyHint: true,
|
|
50
|
+
openWorldHint: true,
|
|
51
|
+
},
|
|
48
52
|
}, this.handler);
|
|
49
53
|
}
|
|
50
54
|
}
|
|
@@ -57,6 +57,10 @@ export class DocsSearchTool extends Tool {
|
|
|
57
57
|
title: 'Search HubSpot Developer Documentation',
|
|
58
58
|
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.',
|
|
59
59
|
inputSchema,
|
|
60
|
+
annotations: {
|
|
61
|
+
readOnlyHint: true,
|
|
62
|
+
openWorldHint: true,
|
|
63
|
+
},
|
|
60
64
|
}, this.handler);
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -65,6 +65,10 @@ export class GetApiUsagePatternsByAppIdTool extends Tool {
|
|
|
65
65
|
title: 'Get API Usage Patterns by App ID',
|
|
66
66
|
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.',
|
|
67
67
|
inputSchema,
|
|
68
|
+
annotations: {
|
|
69
|
+
readOnlyHint: true,
|
|
70
|
+
openWorldHint: true,
|
|
71
|
+
},
|
|
68
72
|
}, this.handler);
|
|
69
73
|
}
|
|
70
74
|
}
|
|
@@ -45,6 +45,10 @@ export class GetApplicationInfoTool extends Tool {
|
|
|
45
45
|
title: 'Get Applications Information',
|
|
46
46
|
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.',
|
|
47
47
|
inputSchema,
|
|
48
|
+
annotations: {
|
|
49
|
+
readOnlyHint: true,
|
|
50
|
+
openWorldHint: true,
|
|
51
|
+
},
|
|
48
52
|
}, this.handler);
|
|
49
53
|
}
|
|
50
54
|
}
|
|
@@ -53,6 +53,10 @@ export class GetConfigValuesTool extends Tool {
|
|
|
53
53
|
This should be called before editing a '-hsmeta.json' file to get the list of possible values and restrictions on those values.
|
|
54
54
|
This will only work for projects with platformVersion 2025.2 and beyond`,
|
|
55
55
|
inputSchema,
|
|
56
|
+
annotations: {
|
|
57
|
+
readOnlyHint: true,
|
|
58
|
+
openWorldHint: false,
|
|
59
|
+
},
|
|
56
60
|
}, this.handler);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -44,6 +44,10 @@ export class GuidedWalkthroughTool extends Tool {
|
|
|
44
44
|
title: 'Guided walkthrough of the CLI',
|
|
45
45
|
description: 'Give the user a guided walkthrough of the HubSpot CLI.',
|
|
46
46
|
inputSchema,
|
|
47
|
+
annotations: {
|
|
48
|
+
readOnlyHint: true,
|
|
49
|
+
openWorldHint: false,
|
|
50
|
+
},
|
|
47
51
|
}, this.handler);
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -67,6 +67,12 @@ export class UploadProjectTools extends Tool {
|
|
|
67
67
|
title: 'Upload HubSpot Project',
|
|
68
68
|
description: 'DO NOT run this tool unless the user specifies they would like to upload the project, it is potentially destructive. Uploads the HubSpot project in current working directory. If the project does not exist, it will be created. MUST be ran from within the project directory.',
|
|
69
69
|
inputSchema,
|
|
70
|
+
annotations: {
|
|
71
|
+
readOnlyHint: false,
|
|
72
|
+
destructiveHint: true,
|
|
73
|
+
idempotentHint: true,
|
|
74
|
+
openWorldHint: true,
|
|
75
|
+
},
|
|
70
76
|
}, this.handler);
|
|
71
77
|
}
|
|
72
78
|
}
|
|
@@ -30,6 +30,10 @@ export class ValidateProjectTool extends Tool {
|
|
|
30
30
|
title: 'Validate HubSpot Project',
|
|
31
31
|
description: 'Validates the HubSpot project and its configuration files. This tool does not need to be ran before uploading the project',
|
|
32
32
|
inputSchema,
|
|
33
|
+
annotations: {
|
|
34
|
+
readOnlyHint: true,
|
|
35
|
+
openWorldHint: false,
|
|
36
|
+
},
|
|
33
37
|
}, this.handler);
|
|
34
38
|
}
|
|
35
39
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { CreateTestAccountTool, } from '../CreateTestAccountTool.js';
|
|
2
|
+
import { runCommandInDir } from '../../../utils/project.js';
|
|
3
|
+
import { addFlag } from '../../../utils/command.js';
|
|
4
|
+
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
5
|
+
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
6
|
+
vi.mock('../../../utils/project');
|
|
7
|
+
vi.mock('../../../utils/command');
|
|
8
|
+
vi.mock('../../../utils/toolUsageTracking');
|
|
9
|
+
vi.mock('../../../utils/feedbackTracking');
|
|
10
|
+
const mockMcpFeedbackRequest = mcpFeedbackRequest;
|
|
11
|
+
const mockRunCommandInDir = runCommandInDir;
|
|
12
|
+
const mockAddFlag = addFlag;
|
|
13
|
+
describe('mcp-server/tools/project/CreateTestAccountTool', () => {
|
|
14
|
+
let mockMcpServer;
|
|
15
|
+
let tool;
|
|
16
|
+
let mockRegisteredTool;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
// @ts-expect-error Not mocking the whole server
|
|
20
|
+
mockMcpServer = {
|
|
21
|
+
registerTool: vi.fn(),
|
|
22
|
+
};
|
|
23
|
+
mockRegisteredTool = {};
|
|
24
|
+
mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
|
|
25
|
+
mockMcpFeedbackRequest.mockResolvedValue('');
|
|
26
|
+
tool = new CreateTestAccountTool(mockMcpServer);
|
|
27
|
+
// Mock addFlag to simulate command building
|
|
28
|
+
mockAddFlag.mockImplementation((command, flag, value) => `${command} --${flag} "${value}"`);
|
|
29
|
+
});
|
|
30
|
+
describe('register', () => {
|
|
31
|
+
it('should register tool with correct parameters', () => {
|
|
32
|
+
const result = tool.register();
|
|
33
|
+
expect(mockMcpServer.registerTool).toHaveBeenCalledWith('create-test-account', expect.objectContaining({
|
|
34
|
+
title: 'Create HubSpot Test Account',
|
|
35
|
+
description: expect.stringContaining('Creates a HubSpot developer test account'),
|
|
36
|
+
inputSchema: expect.any(Object),
|
|
37
|
+
}), expect.any(Function));
|
|
38
|
+
expect(result).toBe(mockRegisteredTool);
|
|
39
|
+
});
|
|
40
|
+
it('should include all key information in description', () => {
|
|
41
|
+
tool.register();
|
|
42
|
+
const registerCall = mockMcpServer.registerTool.mock.calls[0];
|
|
43
|
+
const config = registerCall[1];
|
|
44
|
+
expect(config.description).toContain('test account');
|
|
45
|
+
expect(config.description).toContain('WORKFLOW');
|
|
46
|
+
expect(config.description).toContain('config file');
|
|
47
|
+
expect(config.description).toContain('ALL account details');
|
|
48
|
+
expect(config.description).toContain('non-interactive execution');
|
|
49
|
+
expect(config.description).toContain('FREE, STARTER, PROFESSIONAL, ENTERPRISE');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('handler', () => {
|
|
53
|
+
describe('config file approach', () => {
|
|
54
|
+
const baseInput = {
|
|
55
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
56
|
+
configPath: './test-account.json',
|
|
57
|
+
};
|
|
58
|
+
it('should create test account with config path', async () => {
|
|
59
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
60
|
+
stdout: 'Test account created successfully\nAccount ID: 12345678',
|
|
61
|
+
stderr: '',
|
|
62
|
+
});
|
|
63
|
+
const result = await tool.handler(baseInput);
|
|
64
|
+
expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'config-path', './test-account.json');
|
|
65
|
+
expect(mockRunCommandInDir).toHaveBeenCalledWith('/test/workspace', 'hs test-account create --config-path "./test-account.json"');
|
|
66
|
+
expect(result).toEqual({
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: 'Test account created successfully\nAccount ID: 12345678',
|
|
71
|
+
},
|
|
72
|
+
{ type: 'text', text: '' },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
it('should handle absolute config path', async () => {
|
|
77
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
78
|
+
stdout: 'Account created',
|
|
79
|
+
stderr: '',
|
|
80
|
+
});
|
|
81
|
+
const input = {
|
|
82
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
83
|
+
configPath: '/absolute/path/to/config.json',
|
|
84
|
+
};
|
|
85
|
+
await tool.handler(input);
|
|
86
|
+
expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'config-path', '/absolute/path/to/config.json');
|
|
87
|
+
expect(mockRunCommandInDir).toHaveBeenCalledWith('/test/workspace', 'hs test-account create --config-path "/absolute/path/to/config.json"');
|
|
88
|
+
});
|
|
89
|
+
it('should prioritize config path over flags', async () => {
|
|
90
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
91
|
+
stdout: 'Account created',
|
|
92
|
+
stderr: '',
|
|
93
|
+
});
|
|
94
|
+
const input = {
|
|
95
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
96
|
+
configPath: './test-account.json',
|
|
97
|
+
name: 'FlagAccount',
|
|
98
|
+
description: 'This should be ignored',
|
|
99
|
+
};
|
|
100
|
+
await tool.handler(input);
|
|
101
|
+
expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'config-path', './test-account.json');
|
|
102
|
+
// Should not call addFlag for name or description
|
|
103
|
+
expect(mockAddFlag).not.toHaveBeenCalledWith(expect.anything(), 'name', expect.anything());
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('flag-based approach', () => {
|
|
107
|
+
it('should create test account with just account name', async () => {
|
|
108
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
109
|
+
stdout: 'Test account created successfully',
|
|
110
|
+
stderr: '',
|
|
111
|
+
});
|
|
112
|
+
const input = {
|
|
113
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
114
|
+
name: 'MyTestAccount',
|
|
115
|
+
};
|
|
116
|
+
await tool.handler(input);
|
|
117
|
+
expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'MyTestAccount');
|
|
118
|
+
expect(mockRunCommandInDir).toHaveBeenCalledWith('/test/workspace', 'hs test-account create --name "MyTestAccount"');
|
|
119
|
+
});
|
|
120
|
+
it('should create test account with account name and description', async () => {
|
|
121
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
122
|
+
stdout: 'Test account created',
|
|
123
|
+
stderr: '',
|
|
124
|
+
});
|
|
125
|
+
const input = {
|
|
126
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
127
|
+
name: 'MyTestAccount',
|
|
128
|
+
description: 'Test account for development',
|
|
129
|
+
};
|
|
130
|
+
await tool.handler(input);
|
|
131
|
+
expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'MyTestAccount');
|
|
132
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.stringContaining('name'), 'description', 'Test account for development');
|
|
133
|
+
});
|
|
134
|
+
it('should create test account with specific hub levels', async () => {
|
|
135
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
136
|
+
stdout: 'Test account created',
|
|
137
|
+
stderr: '',
|
|
138
|
+
});
|
|
139
|
+
const input = {
|
|
140
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
141
|
+
name: 'MixedTierAccount',
|
|
142
|
+
marketingLevel: 'PROFESSIONAL',
|
|
143
|
+
salesLevel: 'STARTER',
|
|
144
|
+
contentLevel: 'FREE',
|
|
145
|
+
};
|
|
146
|
+
await tool.handler(input);
|
|
147
|
+
expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'MixedTierAccount');
|
|
148
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.stringContaining('name'), 'marketing-level', 'PROFESSIONAL');
|
|
149
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.stringContaining('marketing-level'), 'sales-level', 'STARTER');
|
|
150
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.stringContaining('sales-level'), 'content-level', 'FREE');
|
|
151
|
+
});
|
|
152
|
+
it('should create test account with all hub levels specified', async () => {
|
|
153
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
154
|
+
stdout: 'Test account created',
|
|
155
|
+
stderr: '',
|
|
156
|
+
});
|
|
157
|
+
const input = {
|
|
158
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
159
|
+
name: 'AllHubsAccount',
|
|
160
|
+
description: 'Full configuration',
|
|
161
|
+
marketingLevel: 'ENTERPRISE',
|
|
162
|
+
opsLevel: 'PROFESSIONAL',
|
|
163
|
+
serviceLevel: 'STARTER',
|
|
164
|
+
salesLevel: 'ENTERPRISE',
|
|
165
|
+
contentLevel: 'PROFESSIONAL',
|
|
166
|
+
};
|
|
167
|
+
await tool.handler(input);
|
|
168
|
+
expect(mockAddFlag).toHaveBeenCalledWith('hs test-account create', 'name', 'AllHubsAccount');
|
|
169
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'description', 'Full configuration');
|
|
170
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'marketing-level', 'ENTERPRISE');
|
|
171
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'ops-level', 'PROFESSIONAL');
|
|
172
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'service-level', 'STARTER');
|
|
173
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'sales-level', 'ENTERPRISE');
|
|
174
|
+
expect(mockAddFlag).toHaveBeenCalledWith(expect.any(String), 'content-level', 'PROFESSIONAL');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('interactive mode', () => {
|
|
178
|
+
it('should ask for parameters when neither config nor name provided', async () => {
|
|
179
|
+
const input = {
|
|
180
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
181
|
+
};
|
|
182
|
+
const result = await tool.handler(input);
|
|
183
|
+
// Should NOT run the command
|
|
184
|
+
expect(mockRunCommandInDir).not.toHaveBeenCalled();
|
|
185
|
+
// Should return a message asking for information
|
|
186
|
+
expect(result).toEqual({
|
|
187
|
+
content: [
|
|
188
|
+
{
|
|
189
|
+
type: 'text',
|
|
190
|
+
text: 'Ask the user for the account config JSON path or the name of the test account to create.',
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
describe('error handling', () => {
|
|
197
|
+
it('should handle command output with stderr warnings', async () => {
|
|
198
|
+
mockRunCommandInDir.mockResolvedValue({
|
|
199
|
+
stdout: 'Test account created successfully',
|
|
200
|
+
stderr: 'Warning: Some non-critical warning message',
|
|
201
|
+
});
|
|
202
|
+
const input = {
|
|
203
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
204
|
+
configPath: './test-account.json',
|
|
205
|
+
};
|
|
206
|
+
const result = await tool.handler(input);
|
|
207
|
+
expect(result).toEqual({
|
|
208
|
+
content: [
|
|
209
|
+
{ type: 'text', text: 'Test account created successfully' },
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: 'Warning: Some non-critical warning message',
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
it('should handle command execution errors', async () => {
|
|
218
|
+
const error = new Error('Failed to create test account');
|
|
219
|
+
mockRunCommandInDir.mockRejectedValue(error);
|
|
220
|
+
const input = {
|
|
221
|
+
absoluteCurrentWorkingDirectory: '/test/workspace',
|
|
222
|
+
configPath: './test-account.json',
|
|
223
|
+
};
|
|
224
|
+
const result = await tool.handler(input);
|
|
225
|
+
expect(result).toEqual({
|
|
226
|
+
content: [{ type: 'text', text: 'Failed to create test account' }],
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|