@hubspot/cli 7.10.0 → 7.11.0-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.
- package/bin/cli.js +5 -4
- package/commands/__tests__/getStarted.test.js +10 -0
- package/commands/__tests__/project.test.js +3 -0
- package/commands/account/__tests__/rename.test.js +10 -3
- package/commands/account/auth.js +10 -14
- package/commands/account/clean.js +11 -19
- package/commands/account/createOverride.js +15 -11
- package/commands/account/info.js +8 -5
- package/commands/account/list.js +15 -19
- package/commands/account/remove.js +23 -22
- package/commands/account/removeOverride.js +6 -6
- package/commands/account/rename.js +2 -2
- package/commands/account/use.js +19 -8
- package/commands/app/__tests__/migrate.test.js +8 -4
- package/commands/app/migrate.js +2 -2
- package/commands/auth.js +18 -14
- package/commands/config/migrate.js +5 -5
- package/commands/customObject/createSchema.js +2 -3
- package/commands/customObject/updateSchema.js +2 -3
- package/commands/getStarted.js +2 -3
- package/commands/hubdb/__tests__/list.test.js +1 -0
- package/commands/hubdb/list.js +2 -2
- package/commands/init.js +36 -32
- package/commands/project/__tests__/deploy.test.js +15 -10
- package/commands/project/__tests__/devUnifiedFlow.test.js +6 -4
- package/commands/project/__tests__/lint.test.js +709 -0
- package/commands/project/__tests__/logs.test.js +4 -0
- package/commands/project/__tests__/validate.test.js +2 -2
- package/commands/project/cloneApp.js +2 -2
- package/commands/project/create.js +20 -14
- package/commands/project/deploy.js +2 -2
- package/commands/project/dev/deprecatedFlow.js +4 -5
- package/commands/project/dev/index.js +6 -3
- package/commands/project/dev/unifiedFlow.js +11 -6
- package/commands/project/lint.d.ts +6 -0
- package/commands/project/lint.js +178 -0
- package/commands/project/logs.js +2 -3
- package/commands/project/migrate.js +4 -13
- package/commands/project/profile/add.js +6 -7
- package/commands/project/profile/delete.js +2 -2
- package/commands/project/upload.js +2 -2
- package/commands/project/validate.js +2 -2
- package/commands/project.js +2 -0
- package/commands/sandbox/__tests__/create.test.js +14 -5
- package/commands/sandbox/create.js +4 -5
- package/commands/sandbox/delete.js +23 -20
- package/commands/testAccount/create.js +2 -2
- package/commands/testAccount/delete.js +9 -8
- package/lang/en.d.ts +53 -12
- package/lang/en.js +63 -16
- package/lib/__tests__/buildAccount.test.js +22 -30
- package/lib/__tests__/commonOpts.test.js +9 -13
- package/lib/__tests__/developerTestAccounts.test.js +29 -17
- package/lib/__tests__/importData.test.js +20 -10
- package/lib/__tests__/oauth.test.js +19 -8
- package/lib/__tests__/sandboxSync.test.js +33 -11
- package/lib/__tests__/sandboxes.test.js +30 -19
- package/lib/__tests__/usageTracking.test.js +10 -10
- package/lib/__tests__/validation.test.js +32 -32
- package/lib/accountTypes.d.ts +9 -9
- package/lib/accountTypes.js +2 -4
- package/lib/app/__tests__/migrate.test.js +15 -0
- package/lib/app/__tests__/migrate_legacy.test.js +9 -0
- package/lib/app/migrate_legacy.d.ts +2 -2
- package/lib/buildAccount.d.ts +4 -4
- package/lib/buildAccount.js +7 -14
- package/lib/commonOpts.js +3 -3
- package/lib/configMigrate.d.ts +2 -2
- package/lib/configMigrate.js +42 -18
- package/lib/configOptions.js +3 -2
- package/lib/developerTestAccounts.d.ts +3 -3
- package/lib/developerTestAccounts.js +4 -7
- package/lib/doctor/DiagnosticInfoBuilder.d.ts +1 -1
- package/lib/doctor/DiagnosticInfoBuilder.js +9 -6
- package/lib/doctor/Doctor.js +4 -3
- package/lib/doctor/__tests__/Diagnosis.test.js +4 -3
- package/lib/doctor/__tests__/DiagnosticInfoBuilder.test.js +17 -9
- package/lib/doctor/__tests__/Doctor.test.js +14 -0
- package/lib/errorHandlers/index.js +8 -3
- package/lib/importData.js +8 -7
- package/lib/links.js +5 -5
- package/lib/middleware/{__test__ → __tests__}/commandTargetingUtils.test.js +3 -3
- package/lib/middleware/{__test__ → __tests__}/configMiddleware.test.js +23 -22
- package/lib/middleware/{__test__ → __tests__}/gitMiddleware.test.js +9 -7
- package/lib/middleware/autoUpdateMiddleware.js +34 -23
- package/lib/middleware/commandTargetingUtils.js +3 -2
- package/lib/middleware/configMiddleware.d.ts +6 -1
- package/lib/middleware/configMiddleware.js +36 -15
- package/lib/middleware/fireAlarmMiddleware.js +4 -15
- package/lib/middleware/gitMiddleware.js +8 -4
- package/lib/oauth.d.ts +2 -2
- package/lib/oauth.js +8 -10
- package/lib/projects/__tests__/AppDevModeInterface.test.js +17 -6
- package/lib/projects/__tests__/DevServerManager.test.js +1 -0
- package/lib/projects/__tests__/LocalDevProcess.test.js +1 -0
- package/lib/projects/__tests__/components.test.js +2 -22
- package/lib/projects/__tests__/deploy.test.js +16 -13
- package/lib/projects/__tests__/uieLinting.test.js +640 -0
- package/lib/projects/add/__tests__/legacyAddComponent.test.js +1 -1
- package/lib/projects/add/__tests__/v2AddComponent.test.js +30 -4
- package/lib/projects/add/legacyAddComponent.js +1 -1
- package/lib/projects/add/v2AddComponent.js +16 -5
- package/lib/projects/components.d.ts +8 -1
- package/lib/projects/components.js +91 -8
- package/lib/projects/create/__tests__/v2.test.js +11 -0
- package/lib/projects/deploy.js +21 -8
- package/lib/projects/localDev/AppDevModeInterface.js +2 -2
- package/lib/projects/localDev/DevServerManager_DEPRECATED.js +11 -3
- package/lib/projects/localDev/LocalDevLogger.js +4 -4
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -3
- package/lib/projects/localDev/helpers/account.d.ts +10 -10
- package/lib/projects/localDev/helpers/account.js +6 -11
- package/lib/projects/localDev/helpers/process.js +5 -3
- package/lib/projects/uieLinting.d.ts +33 -0
- package/lib/projects/uieLinting.js +222 -0
- package/lib/projects/urls.js +5 -6
- package/lib/prompts/__tests__/downloadProjectPrompt.test.js +7 -5
- package/lib/prompts/accountNamePrompt.js +3 -3
- package/lib/prompts/accountsPrompt.d.ts +1 -1
- package/lib/prompts/accountsPrompt.js +6 -7
- package/lib/prompts/confirmImportDataPrompt.js +2 -2
- package/lib/prompts/downloadProjectPrompt.d.ts +1 -0
- package/lib/prompts/downloadProjectPrompt.js +5 -2
- package/lib/prompts/importDataTestAccountSelectPrompt.js +4 -5
- package/lib/prompts/personalAccessKeyPrompt.js +2 -2
- package/lib/prompts/projectDevTargetAccountPrompt.d.ts +3 -3
- package/lib/prompts/projectDevTargetAccountPrompt.js +5 -7
- package/lib/prompts/sandboxesPrompt.js +7 -8
- package/lib/prompts/setAsDefaultAccountPrompt.js +7 -6
- package/lib/sandboxSync.d.ts +2 -2
- package/lib/sandboxSync.js +3 -9
- package/lib/sandboxes.d.ts +4 -4
- package/lib/sandboxes.js +6 -11
- package/lib/serverlessLogs.js +2 -2
- package/lib/theme/__tests__/migrate.test.js +15 -0
- package/lib/ui/SpinniesManager.d.ts +5 -7
- package/lib/ui/SpinniesManager.js +9 -12
- package/lib/ui/__tests__/SpinniesManager.test.d.ts +1 -0
- package/lib/ui/__tests__/SpinniesManager.test.js +489 -0
- package/lib/ui/index.js +6 -3
- package/lib/usageTracking.js +15 -8
- package/lib/validation.js +13 -11
- package/mcp-server/tools/cms/HsCreateFunctionTool.js +4 -2
- package/mcp-server/tools/cms/HsCreateModuleTool.js +4 -2
- package/mcp-server/tools/cms/HsCreateTemplateTool.js +4 -2
- package/mcp-server/tools/cms/HsFunctionLogsTool.js +4 -2
- package/mcp-server/tools/cms/HsListFunctionsTool.js +3 -1
- package/mcp-server/tools/cms/HsListTool.js +3 -1
- package/mcp-server/tools/cms/__tests__/HsCreateFunctionTool.test.js +1 -0
- package/mcp-server/tools/index.js +4 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.js +4 -2
- package/mcp-server/tools/project/CreateProjectTool.js +4 -2
- package/mcp-server/tools/project/CreateTestAccountTool.js +17 -7
- package/mcp-server/tools/project/DeployProjectTool.js +3 -1
- package/mcp-server/tools/project/DocFetchTool.js +6 -4
- package/mcp-server/tools/project/DocsSearchTool.d.ts +1 -1
- package/mcp-server/tools/project/DocsSearchTool.js +10 -8
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.d.ts +1 -1
- package/mcp-server/tools/project/GetApiUsagePatternsByAppIdTool.js +9 -7
- package/mcp-server/tools/project/GetApplicationInfoTool.js +8 -6
- package/mcp-server/tools/project/GetBuildLogsTool.d.ts +26 -0
- package/mcp-server/tools/project/GetBuildLogsTool.js +125 -0
- package/mcp-server/tools/project/GetBuildStatusTool.d.ts +26 -0
- package/mcp-server/tools/project/GetBuildStatusTool.js +166 -0
- package/mcp-server/tools/project/GetConfigValuesTool.d.ts +1 -1
- package/mcp-server/tools/project/GetConfigValuesTool.js +9 -7
- package/mcp-server/tools/project/GuidedWalkthroughTool.d.ts +1 -1
- package/mcp-server/tools/project/GuidedWalkthroughTool.js +5 -3
- package/mcp-server/tools/project/UploadProjectTools.js +3 -1
- package/mcp-server/tools/project/ValidateProjectTool.js +4 -2
- package/mcp-server/tools/project/__tests__/CreateTestAccountTool.test.js +12 -2
- package/mcp-server/tools/project/__tests__/DocFetchTool.test.js +5 -1
- package/mcp-server/tools/project/__tests__/DocsSearchTool.test.js +23 -11
- package/mcp-server/tools/project/__tests__/GetApiUsagePatternsByAppIdTool.test.js +7 -5
- package/mcp-server/tools/project/__tests__/GetApplicationInfoTool.test.js +7 -5
- package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/GetBuildLogsTool.test.js +305 -0
- package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.d.ts +1 -0
- package/mcp-server/tools/project/__tests__/GetBuildStatusTool.test.js +240 -0
- package/mcp-server/tools/project/__tests__/GetConfigValuesTool.test.js +8 -6
- package/mcp-server/utils/__tests__/content.test.js +21 -20
- package/mcp-server/utils/__tests__/feedbackTracking.test.js +34 -28
- package/mcp-server/utils/config.d.ts +1 -0
- package/mcp-server/utils/config.js +10 -0
- package/mcp-server/utils/content.d.ts +1 -1
- package/mcp-server/utils/content.js +2 -2
- package/mcp-server/utils/feedbackTracking.d.ts +1 -1
- package/mcp-server/utils/feedbackTracking.js +3 -3
- package/mcp-server/utils/toolUsageTracking.js +4 -3
- package/package.json +9 -9
- package/ui/components/BoxWithTitle.d.ts +2 -1
- package/ui/components/BoxWithTitle.js +2 -2
- package/ui/components/StatusMessageBoxes.d.ts +5 -4
- package/ui/components/StatusMessageBoxes.js +8 -8
- package/lib/middleware/__test__/notificationsMiddleware.test.js +0 -8
- package/lib/middleware/notificationsMiddleware.d.ts +0 -1
- package/lib/middleware/notificationsMiddleware.js +0 -28
- package/lib/ui/boxen.d.ts +0 -5
- package/lib/ui/boxen.js +0 -26
- package/mcp-server/utils/__tests__/cliConfig.test.js +0 -110
- package/mcp-server/utils/cliConfig.d.ts +0 -1
- package/mcp-server/utils/cliConfig.js +0 -12
- /package/{lib/middleware/__test__/commandTargetingUtils.test.d.ts → commands/project/__tests__/lint.test.d.ts} +0 -0
- /package/lib/middleware/{__test__/configMiddleware.test.d.ts → __tests__/commandTargetingUtils.test.d.ts} +0 -0
- /package/lib/middleware/{__test__/gitMiddleware.test.d.ts → __tests__/configMiddleware.test.d.ts} +0 -0
- /package/lib/middleware/{__test__/notificationsMiddleware.test.d.ts → __tests__/gitMiddleware.test.d.ts} +0 -0
- /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.d.ts +0 -0
- /package/lib/middleware/{__test__ → __tests__}/requestMiddleware.test.js +0 -0
- /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.d.ts +0 -0
- /package/lib/middleware/{__test__ → __tests__}/yargsChecksMiddleware.test.js +0 -0
- /package/{mcp-server/utils/__tests__/cliConfig.test.d.ts → lib/projects/__tests__/uieLinting.test.d.ts} +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Tool } from '../../types.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
4
|
+
import { formatTextContents } from '../../utils/content.js';
|
|
5
|
+
import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
|
|
6
|
+
import { http } from '@hubspot/local-dev-lib/http';
|
|
7
|
+
import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/config.js';
|
|
8
|
+
import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
|
|
9
|
+
import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
10
|
+
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
11
|
+
const TOOL_NAME = 'get-build-logs';
|
|
12
|
+
const PROJECTS_LOGS_API_PATH = 'dfs/logging/v1';
|
|
13
|
+
const inputSchema = {
|
|
14
|
+
absoluteProjectPath,
|
|
15
|
+
absoluteCurrentWorkingDirectory,
|
|
16
|
+
buildId: z
|
|
17
|
+
.number()
|
|
18
|
+
.describe('Build ID to fetch logs for. Use get-build-status to find recent build IDs.'),
|
|
19
|
+
logLevel: z
|
|
20
|
+
.enum(['ERROR', 'WARN', 'INFO', 'ALL'])
|
|
21
|
+
.optional()
|
|
22
|
+
.default('ALL')
|
|
23
|
+
.describe('Filter logs by level. ERROR: Show only errors, WARN: Show only warnings, INFO: Show only info, ALL: Show all logs.'),
|
|
24
|
+
};
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
26
|
+
const inputSchemaZodObject = z.object({ ...inputSchema });
|
|
27
|
+
function flattenLogs(response) {
|
|
28
|
+
const allLogs = [];
|
|
29
|
+
response.logs.forEach(substep => {
|
|
30
|
+
substep.logs.forEach(log => {
|
|
31
|
+
allLogs.push(log);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
return allLogs.sort((a, b) => a.timestamp - b.timestamp);
|
|
35
|
+
}
|
|
36
|
+
function filterLogsByLevel(logs, logLevel) {
|
|
37
|
+
if (logLevel === 'ALL') {
|
|
38
|
+
return logs;
|
|
39
|
+
}
|
|
40
|
+
return logs.filter(log => log.logLevel === logLevel);
|
|
41
|
+
}
|
|
42
|
+
function formatLogs(logs) {
|
|
43
|
+
if (logs.length === 0) {
|
|
44
|
+
return 'No logs found.';
|
|
45
|
+
}
|
|
46
|
+
return logs
|
|
47
|
+
.map(log => {
|
|
48
|
+
const timestamp = new Date(log.timestamp).toLocaleString('en-US', {
|
|
49
|
+
month: 'short',
|
|
50
|
+
day: 'numeric',
|
|
51
|
+
year: 'numeric',
|
|
52
|
+
hour: 'numeric',
|
|
53
|
+
minute: 'numeric',
|
|
54
|
+
second: 'numeric',
|
|
55
|
+
timeZoneName: 'short',
|
|
56
|
+
});
|
|
57
|
+
const component = log.pipelineSubstepName || `Step ${log.pipelineStepId}`;
|
|
58
|
+
return `[${log.logLevel}][${component}] ${timestamp} ${log.message}`;
|
|
59
|
+
})
|
|
60
|
+
.join('\n');
|
|
61
|
+
}
|
|
62
|
+
export class GetBuildLogsTool extends Tool {
|
|
63
|
+
constructor(mcpServer) {
|
|
64
|
+
super(mcpServer);
|
|
65
|
+
}
|
|
66
|
+
async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, buildId, logLevel, }) {
|
|
67
|
+
setupHubSpotConfig(absoluteCurrentWorkingDirectory);
|
|
68
|
+
await trackToolUsage(TOOL_NAME);
|
|
69
|
+
try {
|
|
70
|
+
const accountId = getConfigDefaultAccountIfExists()?.accountId;
|
|
71
|
+
if (!accountId) {
|
|
72
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, 'No account ID found. Please run `hs account auth` to configure an account, or set a default account with `hs account use <account>`');
|
|
73
|
+
}
|
|
74
|
+
const { projectConfig, projectDir } = await getProjectConfig(absoluteProjectPath);
|
|
75
|
+
validateProjectConfig(projectConfig, projectDir);
|
|
76
|
+
const projectName = projectConfig.name;
|
|
77
|
+
const response = await http.get(accountId, {
|
|
78
|
+
url: `${PROJECTS_LOGS_API_PATH}/logs/projects/${encodeURIComponent(projectName)}/builds/${buildId}`,
|
|
79
|
+
});
|
|
80
|
+
const buildLogsResponse = response.data;
|
|
81
|
+
if (!buildLogsResponse.logs || buildLogsResponse.logs.length === 0) {
|
|
82
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, `No logs found for build #${buildId} in project '${projectName}'.`);
|
|
83
|
+
}
|
|
84
|
+
const allLogs = flattenLogs(buildLogsResponse);
|
|
85
|
+
if (allLogs.length === 0) {
|
|
86
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, `No logs found for build #${buildId} in project '${projectName}'.`);
|
|
87
|
+
}
|
|
88
|
+
const filteredLogs = filterLogsByLevel(allLogs, logLevel);
|
|
89
|
+
let output;
|
|
90
|
+
if (filteredLogs.length === 0) {
|
|
91
|
+
// No logs match filter, show all logs instead
|
|
92
|
+
output = `No ${logLevel} level logs found for build #${buildId} in '${projectName}'.\nShowing all logs instead:\n\n${formatLogs(allLogs)}`;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
output = `Logs for build #${buildId} in '${projectName}' (${logLevel} level):\n\n${formatLogs(filteredLogs)}`;
|
|
96
|
+
}
|
|
97
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, output);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
let errorMessage;
|
|
101
|
+
if (isHubSpotHttpError(error)) {
|
|
102
|
+
errorMessage = error.toString();
|
|
103
|
+
}
|
|
104
|
+
else if (error instanceof Error) {
|
|
105
|
+
errorMessage = error.message;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
errorMessage = String(error);
|
|
109
|
+
}
|
|
110
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, errorMessage);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
register() {
|
|
114
|
+
return this.mcpServer.registerTool(TOOL_NAME, {
|
|
115
|
+
title: 'Get HubSpot Project Build Logs',
|
|
116
|
+
description: 'Retrieves build logs for a specific HubSpot project build. Use this to debug build failures by viewing the full build pipeline output. This tool is for more comprehensive troubleshootings or addressing build WARNINGs, build errors should be troubleshooted with get-build-status tool first. Logs can be filtered by level (ERROR, WARN, INFO, or ALL). Use `hs project list-builds` first to identify the build ID and error messages.',
|
|
117
|
+
inputSchema,
|
|
118
|
+
annotations: {
|
|
119
|
+
readOnlyHint: true,
|
|
120
|
+
openWorldHint: true,
|
|
121
|
+
idempotentHint: true,
|
|
122
|
+
},
|
|
123
|
+
}, this.handler);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -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
|
+
absoluteProjectPath: z.ZodString;
|
|
6
|
+
absoluteCurrentWorkingDirectory: z.ZodString;
|
|
7
|
+
buildId: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
limit: number;
|
|
11
|
+
absoluteProjectPath: string;
|
|
12
|
+
absoluteCurrentWorkingDirectory: string;
|
|
13
|
+
buildId?: number | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
absoluteProjectPath: string;
|
|
16
|
+
absoluteCurrentWorkingDirectory: string;
|
|
17
|
+
limit?: number | undefined;
|
|
18
|
+
buildId?: number | undefined;
|
|
19
|
+
}>;
|
|
20
|
+
export type GetBuildStatusInputSchema = z.infer<typeof inputSchemaZodObject>;
|
|
21
|
+
export declare class GetBuildStatusTool extends Tool<GetBuildStatusInputSchema> {
|
|
22
|
+
constructor(mcpServer: McpServer);
|
|
23
|
+
handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, buildId, limit, }: GetBuildStatusInputSchema): Promise<TextContentResponse>;
|
|
24
|
+
register(): RegisteredTool;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Tool } from '../../types.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
4
|
+
import { formatTextContents } from '../../utils/content.js';
|
|
5
|
+
import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
|
|
6
|
+
import { fetchProjectBuilds, getBuildStatus, } from '@hubspot/local-dev-lib/api/projects';
|
|
7
|
+
import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/config.js';
|
|
8
|
+
import moment from 'moment';
|
|
9
|
+
import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constants.js';
|
|
10
|
+
import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
11
|
+
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
12
|
+
const TOOL_NAME = 'get-build-status';
|
|
13
|
+
const inputSchema = {
|
|
14
|
+
absoluteProjectPath,
|
|
15
|
+
absoluteCurrentWorkingDirectory,
|
|
16
|
+
buildId: z
|
|
17
|
+
.number()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Optional: Specific build ID to inspect. If omitted, shows recent builds to help identify the latest build.'),
|
|
20
|
+
limit: z
|
|
21
|
+
.number()
|
|
22
|
+
.optional()
|
|
23
|
+
.default(3)
|
|
24
|
+
.describe('Number of recent builds to fetch when buildId is not specified.'),
|
|
25
|
+
};
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
|
+
const inputSchemaZodObject = z.object({ ...inputSchema });
|
|
28
|
+
function getStatusIcon(status) {
|
|
29
|
+
return status === 'SUCCESS' ? '✓' : '⚠️';
|
|
30
|
+
}
|
|
31
|
+
function formatDuration(startedAt, finishedAt) {
|
|
32
|
+
const duration = moment.duration(moment(finishedAt).diff(moment(startedAt)));
|
|
33
|
+
const days = Math.floor(duration.asDays());
|
|
34
|
+
const hours = duration.hours();
|
|
35
|
+
const minutes = duration.minutes();
|
|
36
|
+
const seconds = duration.seconds();
|
|
37
|
+
const parts = [];
|
|
38
|
+
if (days > 0)
|
|
39
|
+
parts.push(`${days}d`);
|
|
40
|
+
if (hours > 0)
|
|
41
|
+
parts.push(`${hours}h`);
|
|
42
|
+
if (minutes > 0)
|
|
43
|
+
parts.push(`${minutes}m`);
|
|
44
|
+
if (seconds > 0 || parts.length === 0)
|
|
45
|
+
parts.push(`${seconds}s`);
|
|
46
|
+
return parts.join(' ');
|
|
47
|
+
}
|
|
48
|
+
function formatSubbuilds(subbuilds, indent = ' ') {
|
|
49
|
+
const lines = [];
|
|
50
|
+
subbuilds.forEach(sub => {
|
|
51
|
+
const icon = getStatusIcon(sub.status);
|
|
52
|
+
lines.push(`${indent}${icon} ${sub.buildName} (${sub.buildType}): ${sub.status}`);
|
|
53
|
+
if (sub.errorMessage) {
|
|
54
|
+
lines.push(`${indent} Error: ${sub.errorMessage}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return lines;
|
|
58
|
+
}
|
|
59
|
+
function formatBuildList(builds) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
builds.forEach((build, index) => {
|
|
62
|
+
const icon = getStatusIcon(build.status);
|
|
63
|
+
const timeAgo = moment(build.finishedAt).fromNow();
|
|
64
|
+
const duration = formatDuration(build.startedAt, build.finishedAt);
|
|
65
|
+
lines.push(`Build #${build.buildId} - ${build.status} ${icon}`);
|
|
66
|
+
lines.push(` Finished: ${timeAgo} (Duration: ${duration})`);
|
|
67
|
+
if (build.uploadMessage) {
|
|
68
|
+
lines.push(` Message: ${build.uploadMessage}`);
|
|
69
|
+
}
|
|
70
|
+
if (build.status === 'FAILURE' && build.buildErrorMessage) {
|
|
71
|
+
lines.push(` Error: ${build.buildErrorMessage}`);
|
|
72
|
+
}
|
|
73
|
+
if (build.subbuildStatuses.length > 0) {
|
|
74
|
+
lines.push(` Subbuilds:`);
|
|
75
|
+
lines.push(...formatSubbuilds(build.subbuildStatuses, ' '));
|
|
76
|
+
}
|
|
77
|
+
if (index < builds.length - 1) {
|
|
78
|
+
lines.push('');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return lines.join('\n');
|
|
82
|
+
}
|
|
83
|
+
function formatBuildDetails(build) {
|
|
84
|
+
const lines = [];
|
|
85
|
+
const icon = getStatusIcon(build.status);
|
|
86
|
+
lines.push(`Build #${build.buildId} Details\n`);
|
|
87
|
+
lines.push(`Status: ${build.status} ${icon}`);
|
|
88
|
+
lines.push(`Platform Version: ${build.platformVersion}`);
|
|
89
|
+
lines.push(`Started: ${moment(build.startedAt).format('YYYY-MM-DD HH:mm:ss UTC')}`);
|
|
90
|
+
lines.push(`Finished: ${moment(build.finishedAt).format('YYYY-MM-DD HH:mm:ss UTC')}`);
|
|
91
|
+
lines.push(`Duration: ${formatDuration(build.startedAt, build.finishedAt)}`);
|
|
92
|
+
if (build.uploadMessage) {
|
|
93
|
+
lines.push(`\nUpload Message:\n${build.uploadMessage}`);
|
|
94
|
+
}
|
|
95
|
+
if (build.buildErrorMessage) {
|
|
96
|
+
lines.push(`\nBuild Error:\n${build.buildErrorMessage}`);
|
|
97
|
+
}
|
|
98
|
+
if (build.subbuildStatuses.length > 0) {
|
|
99
|
+
lines.push(`\nSubbuilds:`);
|
|
100
|
+
lines.push(...formatSubbuilds(build.subbuildStatuses));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
lines.push(`\nSubbuilds: None`);
|
|
104
|
+
}
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
export class GetBuildStatusTool extends Tool {
|
|
108
|
+
constructor(mcpServer) {
|
|
109
|
+
super(mcpServer);
|
|
110
|
+
}
|
|
111
|
+
async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, buildId, limit, }) {
|
|
112
|
+
setupHubSpotConfig(absoluteCurrentWorkingDirectory);
|
|
113
|
+
await trackToolUsage(TOOL_NAME);
|
|
114
|
+
try {
|
|
115
|
+
const accountId = getConfigDefaultAccountIfExists()?.accountId;
|
|
116
|
+
if (!accountId) {
|
|
117
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, 'No account ID found. Please run `hs account auth` to configure an account, or set a default account with `hs account use <account>`');
|
|
118
|
+
}
|
|
119
|
+
const { projectConfig, projectDir } = await getProjectConfig(absoluteProjectPath);
|
|
120
|
+
validateProjectConfig(projectConfig, projectDir);
|
|
121
|
+
const projectName = projectConfig.name;
|
|
122
|
+
let output;
|
|
123
|
+
if (buildId) {
|
|
124
|
+
const response = await getBuildStatus(accountId, projectName, buildId);
|
|
125
|
+
const build = response.data;
|
|
126
|
+
output = formatBuildDetails(build);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const response = await fetchProjectBuilds(accountId, projectName, {
|
|
130
|
+
limit,
|
|
131
|
+
});
|
|
132
|
+
const { results } = response.data;
|
|
133
|
+
if (!results || results.length === 0) {
|
|
134
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, `No builds found for project '${projectName}'.`);
|
|
135
|
+
}
|
|
136
|
+
output = `Recent builds for '${projectName}':\n\n${formatBuildList(results)}`;
|
|
137
|
+
}
|
|
138
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, output);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
let errorMessage;
|
|
142
|
+
if (isHubSpotHttpError(error)) {
|
|
143
|
+
errorMessage = error.toString();
|
|
144
|
+
}
|
|
145
|
+
else if (error instanceof Error) {
|
|
146
|
+
errorMessage = error.message;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
errorMessage = String(error);
|
|
150
|
+
}
|
|
151
|
+
return formatTextContents(absoluteCurrentWorkingDirectory, errorMessage);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
register() {
|
|
155
|
+
return this.mcpServer.registerTool(TOOL_NAME, {
|
|
156
|
+
title: 'Get HubSpot Projects Build Status and Errors',
|
|
157
|
+
description: 'Retrieves build status and error messages for HubSpot projects. When buildId is omitted, shows recent builds with their status(default 3) - use this to find the latest builds when troubleshooting. When buildId is provided, shows detailed error information for that specific build. Displays buildErrorMessage and subbuild failures to help diagnose build issues.',
|
|
158
|
+
inputSchema,
|
|
159
|
+
annotations: {
|
|
160
|
+
readOnlyHint: true,
|
|
161
|
+
openWorldHint: true,
|
|
162
|
+
idempotentHint: true,
|
|
163
|
+
},
|
|
164
|
+
}, this.handler);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -17,7 +17,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
17
17
|
type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
|
|
18
18
|
export declare class GetConfigValuesTool extends Tool<InputSchemaType> {
|
|
19
19
|
constructor(mcpServer: McpServer);
|
|
20
|
-
handler({
|
|
20
|
+
handler({ platformVersion, featureType, absoluteCurrentWorkingDirectory, }: InputSchemaType): Promise<TextContentResponse>;
|
|
21
21
|
register(): RegisteredTool;
|
|
22
22
|
}
|
|
23
23
|
export {};
|
|
@@ -4,7 +4,8 @@ import { formatTextContents } from '../../utils/content.js';
|
|
|
4
4
|
import { absoluteCurrentWorkingDirectory } from './constants.js';
|
|
5
5
|
import { getIntermediateRepresentationSchema, mapToInternalType, } from '@hubspot/project-parsing-lib';
|
|
6
6
|
import { isV2Project } from '../../../lib/projects/platformVersion.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
8
|
+
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
8
9
|
const inputSchema = {
|
|
9
10
|
absoluteCurrentWorkingDirectory,
|
|
10
11
|
platformVersion: z
|
|
@@ -23,15 +24,16 @@ export class GetConfigValuesTool extends Tool {
|
|
|
23
24
|
constructor(mcpServer) {
|
|
24
25
|
super(mcpServer);
|
|
25
26
|
}
|
|
26
|
-
async handler({
|
|
27
|
+
async handler({ platformVersion, featureType, absoluteCurrentWorkingDirectory, }) {
|
|
28
|
+
setupHubSpotConfig(absoluteCurrentWorkingDirectory);
|
|
27
29
|
try {
|
|
28
30
|
if (!isV2Project(platformVersion)) {
|
|
29
|
-
return formatTextContents(
|
|
31
|
+
return formatTextContents(`Can only be used on projects with a minimum platformVersion of 2025.2`);
|
|
30
32
|
}
|
|
31
|
-
const accountId =
|
|
33
|
+
const accountId = getConfigDefaultAccountIfExists()?.accountId;
|
|
32
34
|
if (!accountId) {
|
|
33
35
|
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(
|
|
36
|
+
return formatTextContents(authErrorMessage);
|
|
35
37
|
}
|
|
36
38
|
const schema = await getIntermediateRepresentationSchema({
|
|
37
39
|
platformVersion,
|
|
@@ -40,11 +42,11 @@ export class GetConfigValuesTool extends Tool {
|
|
|
40
42
|
});
|
|
41
43
|
const internalComponentType = mapToInternalType(featureType);
|
|
42
44
|
if (schema[internalComponentType]) {
|
|
43
|
-
return formatTextContents(
|
|
45
|
+
return formatTextContents(JSON.stringify({ config: schema[internalComponentType] }));
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
catch (error) { }
|
|
47
|
-
return formatTextContents(
|
|
49
|
+
return formatTextContents(`Unable to locate JSON schema for type ${featureType}`);
|
|
48
50
|
}
|
|
49
51
|
register() {
|
|
50
52
|
return this.mcpServer.registerTool(toolName, {
|
|
@@ -14,7 +14,7 @@ declare const inputSchemaZodObject: z.ZodObject<{
|
|
|
14
14
|
type InputSchemaType = z.infer<typeof inputSchemaZodObject>;
|
|
15
15
|
export declare class GuidedWalkthroughTool extends Tool<InputSchemaType> {
|
|
16
16
|
constructor(mcpServer: McpServer);
|
|
17
|
-
handler({
|
|
17
|
+
handler({ command, absoluteCurrentWorkingDirectory, }: InputSchemaType): Promise<TextContentResponse>;
|
|
18
18
|
register(): RegisteredTool;
|
|
19
19
|
}
|
|
20
20
|
export {};
|
|
@@ -4,6 +4,7 @@ import { execAsync } from '../../utils/command.js';
|
|
|
4
4
|
import { formatTextContents } from '../../utils/content.js';
|
|
5
5
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
6
6
|
import { absoluteCurrentWorkingDirectory } from './constants.js';
|
|
7
|
+
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
7
8
|
const nextCommands = {
|
|
8
9
|
'hs init': 'hs auth',
|
|
9
10
|
'hs auth': 'hs project create',
|
|
@@ -31,13 +32,14 @@ export class GuidedWalkthroughTool extends Tool {
|
|
|
31
32
|
constructor(mcpServer) {
|
|
32
33
|
super(mcpServer);
|
|
33
34
|
}
|
|
34
|
-
async handler({
|
|
35
|
+
async handler({ command, absoluteCurrentWorkingDirectory, }) {
|
|
36
|
+
setupHubSpotConfig(absoluteCurrentWorkingDirectory);
|
|
35
37
|
await trackToolUsage(toolName);
|
|
36
38
|
if (command) {
|
|
37
39
|
const { stdout } = await execAsync(`${command} --help`);
|
|
38
|
-
return formatTextContents(
|
|
40
|
+
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]}` : ''}`);
|
|
39
41
|
}
|
|
40
|
-
return formatTextContents(
|
|
42
|
+
return formatTextContents('Is there another command you would like to learn more about?');
|
|
41
43
|
}
|
|
42
44
|
register() {
|
|
43
45
|
return this.mcpServer.registerTool(toolName, {
|
|
@@ -8,6 +8,7 @@ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constan
|
|
|
8
8
|
import { formatTextContent, formatTextContents } from '../../utils/content.js';
|
|
9
9
|
import { trackToolUsage } from '../../utils/toolUsageTracking.js';
|
|
10
10
|
import { addFlag } from '../../utils/command.js';
|
|
11
|
+
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
11
12
|
const inputSchema = {
|
|
12
13
|
absoluteProjectPath,
|
|
13
14
|
absoluteCurrentWorkingDirectory,
|
|
@@ -28,6 +29,7 @@ export class UploadProjectTools extends Tool {
|
|
|
28
29
|
super(mcpServer);
|
|
29
30
|
}
|
|
30
31
|
async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, profile, uploadMessage, }) {
|
|
32
|
+
setupHubSpotConfig(absoluteCurrentWorkingDirectory);
|
|
31
33
|
await trackToolUsage(toolName);
|
|
32
34
|
let command = addFlag('hs project upload', 'force-create', true);
|
|
33
35
|
const content = [];
|
|
@@ -60,7 +62,7 @@ export class UploadProjectTools extends Tool {
|
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
const { stdout, stderr } = await runCommandInDir(absoluteProjectPath, command);
|
|
63
|
-
return formatTextContents(
|
|
65
|
+
return formatTextContents(stdout, stderr);
|
|
64
66
|
}
|
|
65
67
|
register() {
|
|
66
68
|
return this.mcpServer.registerTool(toolName, {
|
|
@@ -4,6 +4,7 @@ import { absoluteCurrentWorkingDirectory, absoluteProjectPath, } from './constan
|
|
|
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
|
+
import { setupHubSpotConfig } from '../../utils/config.js';
|
|
7
8
|
const inputSchema = {
|
|
8
9
|
absoluteProjectPath,
|
|
9
10
|
absoluteCurrentWorkingDirectory,
|
|
@@ -16,13 +17,14 @@ export class ValidateProjectTool extends Tool {
|
|
|
16
17
|
super(mcpServer);
|
|
17
18
|
}
|
|
18
19
|
async handler({ absoluteProjectPath, absoluteCurrentWorkingDirectory, }) {
|
|
20
|
+
setupHubSpotConfig(absoluteCurrentWorkingDirectory);
|
|
19
21
|
await trackToolUsage(toolName);
|
|
20
22
|
try {
|
|
21
23
|
const { stdout, stderr } = await runCommandInDir(absoluteProjectPath, 'hs project validate');
|
|
22
|
-
return formatTextContents(
|
|
24
|
+
return formatTextContents(stdout, stderr);
|
|
23
25
|
}
|
|
24
26
|
catch (error) {
|
|
25
|
-
return formatTextContents(
|
|
27
|
+
return formatTextContents(error instanceof Error ? error.message : `${error}`);
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
register() {
|
|
@@ -3,16 +3,19 @@ import { runCommandInDir } from '../../../utils/project.js';
|
|
|
3
3
|
import { addFlag } from '../../../utils/command.js';
|
|
4
4
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
+
import * as config from '@hubspot/local-dev-lib/config';
|
|
6
7
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
8
|
vi.mock('../../../utils/project');
|
|
8
9
|
vi.mock('../../../utils/command');
|
|
9
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
10
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
11
12
|
vi.mock('fs');
|
|
13
|
+
vi.mock('@hubspot/local-dev-lib/config');
|
|
12
14
|
const mockMcpFeedbackRequest = mcpFeedbackRequest;
|
|
13
15
|
const mockRunCommandInDir = runCommandInDir;
|
|
14
16
|
const mockAddFlag = addFlag;
|
|
15
17
|
const mockReadFileSync = fs.readFileSync;
|
|
18
|
+
const mockGetConfigAccountByName = vi.spyOn(config, 'getConfigAccountByName');
|
|
16
19
|
describe('mcp-server/tools/project/CreateTestAccountTool', () => {
|
|
17
20
|
let mockMcpServer;
|
|
18
21
|
let tool;
|
|
@@ -35,6 +38,8 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
|
|
|
35
38
|
description: 'Test description',
|
|
36
39
|
marketingLevel: 'PROFESSIONAL',
|
|
37
40
|
}));
|
|
41
|
+
// @ts-expect-error breaking things
|
|
42
|
+
mockGetConfigAccountByName.mockReturnValue(undefined);
|
|
38
43
|
});
|
|
39
44
|
describe('register', () => {
|
|
40
45
|
it('should register tool with correct parameters', () => {
|
|
@@ -80,6 +85,7 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
|
|
|
80
85
|
expect(mockRunCommandInDir).toHaveBeenCalledWith('/test/workspace', 'hs test-account create --config-path "./test-account.json"');
|
|
81
86
|
expect(result).toEqual({
|
|
82
87
|
content: [
|
|
88
|
+
{ type: 'text', text: '/test/workspace' },
|
|
83
89
|
{
|
|
84
90
|
type: 'text',
|
|
85
91
|
text: 'Test account created successfully\nAccount ID: 12345678',
|
|
@@ -350,7 +356,7 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
|
|
|
350
356
|
const result = await tool.handler(input);
|
|
351
357
|
expect(mockRunCommandInDir).toHaveBeenCalled();
|
|
352
358
|
expect(mockAddFlag).toHaveBeenCalledTimes(7);
|
|
353
|
-
expect(result.content[
|
|
359
|
+
expect(result.content[1]).toEqual({
|
|
354
360
|
type: 'text',
|
|
355
361
|
text: 'Test account created with defaults',
|
|
356
362
|
});
|
|
@@ -413,6 +419,7 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
|
|
|
413
419
|
const result = await tool.handler(input);
|
|
414
420
|
expect(result).toEqual({
|
|
415
421
|
content: [
|
|
422
|
+
{ type: 'text', text: '/test/workspace' },
|
|
416
423
|
{ type: 'text', text: 'Test account created successfully' },
|
|
417
424
|
{
|
|
418
425
|
type: 'text',
|
|
@@ -436,7 +443,10 @@ describe('mcp-server/tools/project/CreateTestAccountTool', () => {
|
|
|
436
443
|
};
|
|
437
444
|
const result = await tool.handler(input);
|
|
438
445
|
expect(result).toEqual({
|
|
439
|
-
content: [
|
|
446
|
+
content: [
|
|
447
|
+
{ type: 'text', text: '/test/workspace' },
|
|
448
|
+
{ type: 'text', text: 'Failed to create test account' },
|
|
449
|
+
],
|
|
440
450
|
});
|
|
441
451
|
});
|
|
442
452
|
});
|
|
@@ -2,12 +2,15 @@ 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
4
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
5
|
+
import { trackToolUsage } from '../../../utils/toolUsageTracking.js';
|
|
5
6
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
6
7
|
vi.mock('@hubspot/local-dev-lib/http/unauthed');
|
|
7
8
|
vi.mock('@hubspot/local-dev-lib/errors/index');
|
|
8
|
-
vi.mock('
|
|
9
|
+
vi.mock('@hubspot/local-dev-lib/config');
|
|
10
|
+
vi.mock('../../../utils/toolUsageTracking');
|
|
9
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
10
12
|
const mockMcpFeedbackRequest = mcpFeedbackRequest;
|
|
13
|
+
const mockTrackToolUsage = trackToolUsage;
|
|
11
14
|
const mockHttp = http;
|
|
12
15
|
const mockIsHubSpotHttpError = vi.mocked(isHubSpotHttpError);
|
|
13
16
|
describe('mcp-server/tools/project/DocFetchTool', () => {
|
|
@@ -23,6 +26,7 @@ describe('mcp-server/tools/project/DocFetchTool', () => {
|
|
|
23
26
|
mockRegisteredTool = {};
|
|
24
27
|
mockMcpServer.registerTool.mockReturnValue(mockRegisteredTool);
|
|
25
28
|
mockMcpFeedbackRequest.mockResolvedValue('');
|
|
29
|
+
mockTrackToolUsage.mockResolvedValue(undefined);
|
|
26
30
|
tool = new DocFetchTool(mockMcpServer);
|
|
27
31
|
});
|
|
28
32
|
describe('register', () => {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
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
|
-
import {
|
|
4
|
+
import { getConfigDefaultAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
5
5
|
import { mcpFeedbackRequest } from '../../../utils/feedbackTracking.js';
|
|
6
6
|
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
7
|
vi.mock('@hubspot/local-dev-lib/http');
|
|
8
8
|
vi.mock('@hubspot/local-dev-lib/errors/index');
|
|
9
|
+
vi.mock('@hubspot/local-dev-lib/config');
|
|
9
10
|
vi.mock('../../../utils/toolUsageTracking');
|
|
10
|
-
vi.mock('../../../utils/cliConfig.js');
|
|
11
11
|
vi.mock('../../../utils/feedbackTracking');
|
|
12
12
|
const mockMcpFeedbackRequest = mcpFeedbackRequest;
|
|
13
13
|
const mockHttp = http;
|
|
14
|
-
const mockGetAccountIdFromCliConfig = getAccountIdFromCliConfig;
|
|
15
14
|
const mockIsHubSpotHttpError = vi.mocked(isHubSpotHttpError);
|
|
15
|
+
const mockGetConfigDefaultAccountIfExists = getConfigDefaultAccountIfExists;
|
|
16
16
|
describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
17
17
|
let mockMcpServer;
|
|
18
18
|
let tool;
|
|
@@ -45,7 +45,7 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
45
45
|
absoluteCurrentWorkingDirectory: '/foo',
|
|
46
46
|
};
|
|
47
47
|
it('should return auth error message when no account ID is found', async () => {
|
|
48
|
-
|
|
48
|
+
mockGetConfigDefaultAccountIfExists.mockReturnValue(undefined);
|
|
49
49
|
const result = await tool.handler(mockInput);
|
|
50
50
|
expect(result).toEqual({
|
|
51
51
|
content: [
|
|
@@ -57,7 +57,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
it('should return successful results when docs are found', async () => {
|
|
60
|
-
|
|
60
|
+
mockGetConfigDefaultAccountIfExists.mockReturnValue({
|
|
61
|
+
accountId: 12345,
|
|
62
|
+
});
|
|
61
63
|
const mockResponse = {
|
|
62
64
|
results: [
|
|
63
65
|
{
|
|
@@ -81,7 +83,7 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
81
83
|
data: mockResponse,
|
|
82
84
|
});
|
|
83
85
|
const result = await tool.handler(mockInput);
|
|
84
|
-
expect(
|
|
86
|
+
expect(mockGetConfigDefaultAccountIfExists).toHaveBeenCalled();
|
|
85
87
|
expect(mockHttp.post).toHaveBeenCalledWith(12345, {
|
|
86
88
|
url: 'dev/docs/llms/v1/docs-search',
|
|
87
89
|
data: {
|
|
@@ -109,7 +111,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
109
111
|
expect(resultText).toContain('Test content 2');
|
|
110
112
|
});
|
|
111
113
|
it('should return no results message when no documentation is found', async () => {
|
|
112
|
-
|
|
114
|
+
mockGetConfigDefaultAccountIfExists.mockReturnValue({
|
|
115
|
+
accountId: 12345,
|
|
116
|
+
});
|
|
113
117
|
const mockResponse = {
|
|
114
118
|
results: [],
|
|
115
119
|
};
|
|
@@ -128,7 +132,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
128
132
|
});
|
|
129
133
|
});
|
|
130
134
|
it('should return no results message when results is null', async () => {
|
|
131
|
-
|
|
135
|
+
mockGetConfigDefaultAccountIfExists.mockReturnValue({
|
|
136
|
+
accountId: 12345,
|
|
137
|
+
});
|
|
132
138
|
const mockResponse = {
|
|
133
139
|
results: null,
|
|
134
140
|
};
|
|
@@ -147,7 +153,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
147
153
|
});
|
|
148
154
|
});
|
|
149
155
|
it('should handle HubSpot HTTP errors', async () => {
|
|
150
|
-
|
|
156
|
+
mockGetConfigDefaultAccountIfExists.mockReturnValue({
|
|
157
|
+
accountId: 12345,
|
|
158
|
+
});
|
|
151
159
|
const mockError = {
|
|
152
160
|
toString: () => 'HubSpot API Error: 404 Not Found',
|
|
153
161
|
};
|
|
@@ -164,7 +172,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
164
172
|
});
|
|
165
173
|
});
|
|
166
174
|
it('should handle generic errors', async () => {
|
|
167
|
-
|
|
175
|
+
mockGetConfigDefaultAccountIfExists.mockReturnValue({
|
|
176
|
+
accountId: 12345,
|
|
177
|
+
});
|
|
168
178
|
const mockError = new Error('Network error');
|
|
169
179
|
mockHttp.post.mockRejectedValue(mockError);
|
|
170
180
|
mockIsHubSpotHttpError.mockReturnValue(false);
|
|
@@ -179,7 +189,9 @@ describe('mcp-server/tools/project/DocsSearchTool', () => {
|
|
|
179
189
|
});
|
|
180
190
|
});
|
|
181
191
|
it('should handle non-Error rejections', async () => {
|
|
182
|
-
|
|
192
|
+
mockGetConfigDefaultAccountIfExists.mockReturnValue({
|
|
193
|
+
accountId: 12345,
|
|
194
|
+
});
|
|
183
195
|
mockHttp.post.mockRejectedValue('String error');
|
|
184
196
|
mockIsHubSpotHttpError.mockReturnValue(false);
|
|
185
197
|
const result = await tool.handler(mockInput);
|