@hubspot/cli 7.7.0-experimental.0 → 7.7.0-experimental.2

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.
@@ -1,5 +1,5 @@
1
- import { MCPTool } from "mcp-framework";
2
- import { z } from "zod";
1
+ import { MCPTool } from 'mcp-framework';
2
+ import { z } from 'zod';
3
3
  interface GenerateAppComponentInput {
4
4
  appName: string;
5
5
  uid: string;
@@ -1,16 +1,16 @@
1
- import { MCPTool } from "mcp-framework";
2
- import { z } from "zod";
1
+ import { MCPTool } from 'mcp-framework';
2
+ import { z } from 'zod';
3
3
  class GenerateAppComponentTool extends MCPTool {
4
- name = "generateAppComponent";
5
- description = "Generates an app component (top-level) with proper folder structure and hsmeta.json configuration file. Apps provide OAuth authentication that sub-components depend on.";
4
+ name = 'generateAppComponent';
5
+ description = 'Generates an app component (top-level) with proper folder structure and hsmeta.json configuration file. Apps provide OAuth authentication that sub-components depend on.';
6
6
  schema = {
7
7
  appName: {
8
8
  type: z.string(),
9
- description: "Name of the app (used for file naming and default display name)",
9
+ description: 'Name of the app (used for file naming and default display name)',
10
10
  },
11
11
  uid: {
12
12
  type: z.string(),
13
- description: "Unique identifier for the app component (must be unique per-project)",
13
+ description: 'Unique identifier for the app component (must be unique per-project)',
14
14
  },
15
15
  description: {
16
16
  type: z.string().optional(),
@@ -18,11 +18,11 @@ class GenerateAppComponentTool extends MCPTool {
18
18
  },
19
19
  displayName: {
20
20
  type: z.string().optional(),
21
- description: "Display name of the app. Defaults to the appName",
21
+ description: 'Display name of the app. Defaults to the appName',
22
22
  },
23
23
  distribution: {
24
24
  type: z.string().optional(),
25
- description: "Distribution description. Defaults to 'A public app'",
25
+ description: "Distribution type (one of 'marketplace' or 'private'). Defaults to 'marketplace'",
26
26
  },
27
27
  supportEmail: {
28
28
  type: z.string().optional(),
@@ -46,11 +46,11 @@ class GenerateAppComponentTool extends MCPTool {
46
46
  },
47
47
  requiredScopes: {
48
48
  type: z.array(z.string()).optional(),
49
- description: "Required OAuth scopes. Consider what sub-components you plan to add. Defaults to basic CRM contact scopes",
49
+ description: 'Required OAuth scopes. Consider what sub-components you plan to add. Defaults to basic CRM contact scopes',
50
50
  },
51
51
  optionalScopes: {
52
52
  type: z.array(z.string()).optional(),
53
- description: "Optional OAuth scopes. Defaults to empty array",
53
+ description: 'Optional OAuth scopes. Defaults to empty array',
54
54
  },
55
55
  permittedFetchUrls: {
56
56
  type: z.array(z.string()).optional(),
@@ -62,23 +62,23 @@ class GenerateAppComponentTool extends MCPTool {
62
62
  },
63
63
  };
64
64
  async execute(input) {
65
- const { appName, uid, description = "A public app", displayName = appName, distribution = "A public app", supportEmail = "support@example.com", documentationUrl = "https://example.com/docs", supportUrl = "https://example.com/support", supportPhone = "+18005555555", redirectUrls = ["http://localhost:3000/oauth-callback"], requiredScopes = [
66
- "crm.objects.contacts.read",
67
- "crm.objects.contacts.write",
68
- ], optionalScopes = [], permittedFetchUrls = ["https://api.example.com"], plannedFeatures = [], } = input;
65
+ const { appName, uid, description = 'A public app', displayName = appName, distribution = 'marketplace', supportEmail = 'support@example.com', documentationUrl = 'https://example.com/docs', supportUrl = 'https://example.com/support', supportPhone = '+18005555555', redirectUrls = ['http://localhost:3000/oauth-callback'], requiredScopes = [
66
+ 'crm.objects.contacts.read',
67
+ 'crm.objects.contacts.write',
68
+ ], optionalScopes = [], permittedFetchUrls = ['https://api.example.com'], plannedFeatures = [], } = input;
69
69
  // Generate the sanitized app name for file naming
70
- const sanitizedAppName = appName.toLowerCase().replace(/\s+/g, "-");
70
+ const sanitizedAppName = appName.toLowerCase().replace(/\s+/g, '-');
71
71
  const fileName = `${sanitizedAppName}-hsmeta.json`;
72
- const folderPath = "src/app";
72
+ const folderPath = 'src/app';
73
73
  const appConfig = {
74
74
  uid: uid,
75
- type: "app",
75
+ type: 'app',
76
76
  config: {
77
77
  description: description,
78
78
  name: displayName,
79
79
  distribution: distribution,
80
80
  auth: {
81
- type: "oauth",
81
+ type: 'oauth',
82
82
  redirectUrls: redirectUrls,
83
83
  requiredScopes: requiredScopes,
84
84
  optionalScopes: optionalScopes,
@@ -108,8 +108,8 @@ class GenerateAppComponentTool extends MCPTool {
108
108
  fileName: fileName,
109
109
  fullPath: `${folderPath}/${fileName}`,
110
110
  architecture: {
111
- componentType: "top-level",
112
- role: "authentication-provider",
111
+ componentType: 'top-level',
112
+ role: 'authentication-provider',
113
113
  singularComponent: true,
114
114
  dependents: "All sub-components (cards, functions, settings, etc.) will depend on this app's authentication",
115
115
  },
@@ -121,8 +121,8 @@ class GenerateAppComponentTool extends MCPTool {
121
121
  `This is a TOP-LEVEL component that provides OAuth authentication`,
122
122
  `Sub-components (features) will live within ${folderPath}/ and depend on this app's authentication`,
123
123
  `The app component uid: ${uid} must be unique within your project`,
124
- "Consider what sub-components you plan to add and ensure sufficient OAuth scopes",
125
- "Typically there is one app component per project (singular component)",
124
+ 'Consider what sub-components you plan to add and ensure sufficient OAuth scopes',
125
+ 'Typically there is one app component per project (singular component)',
126
126
  ],
127
127
  };
128
128
  }
@@ -130,52 +130,52 @@ class GenerateAppComponentTool extends MCPTool {
130
130
  const recommendations = [];
131
131
  if (plannedFeatures.length === 0) {
132
132
  return [
133
- "Consider what sub-components you plan to add to determine required OAuth scopes",
134
- "Cards typically need CRM object read access",
135
- "Functions may need broader API access depending on functionality",
136
- "Webhooks may need specific event subscription scopes",
133
+ 'Consider what sub-components you plan to add to determine required OAuth scopes',
134
+ 'Cards typically need CRM object read access',
135
+ 'Functions may need broader API access depending on functionality',
136
+ 'Webhooks may need specific event subscription scopes',
137
137
  ];
138
138
  }
139
139
  const scopeMap = {
140
140
  cards: [
141
- "Cards typically need: crm.objects.contacts.read, crm.objects.companies.read, crm.objects.deals.read",
141
+ 'Cards typically need: crm.objects.contacts.read, crm.objects.companies.read, crm.objects.deals.read',
142
142
  ],
143
143
  functions: [
144
- "Functions may need: broader API access depending on functionality",
145
- "Consider: crm.objects.*.read, crm.objects.*.write if manipulating CRM data",
144
+ 'Functions may need: broader API access depending on functionality',
145
+ 'Consider: crm.objects.*.read, crm.objects.*.write if manipulating CRM data',
146
146
  ],
147
147
  webhooks: [
148
- "Webhooks may need: specific event subscription scopes",
149
- "Consider: webhooks scope and relevant object scopes for events you want to receive",
148
+ 'Webhooks may need: specific event subscription scopes',
149
+ 'Consider: webhooks scope and relevant object scopes for events you want to receive',
150
150
  ],
151
151
  settings: [
152
- "Settings typically need: minimal scopes, may inherit from other components",
152
+ 'Settings typically need: minimal scopes, may inherit from other components',
153
153
  ],
154
- "marketing-events": [
155
- "Marketing events may need: marketing event creation and management scopes",
154
+ 'marketing-events': [
155
+ 'Marketing events may need: marketing event creation and management scopes',
156
156
  ],
157
- "timeline-events": [
158
- "Timeline events may need: timeline read/write scopes for the relevant objects",
157
+ 'timeline-events': [
158
+ 'Timeline events may need: timeline read/write scopes for the relevant objects',
159
159
  ],
160
- "workflow-actions": [
161
- "Workflow actions may need: workflow and automation related scopes",
160
+ 'workflow-actions': [
161
+ 'Workflow actions may need: workflow and automation related scopes',
162
162
  ],
163
163
  };
164
- plannedFeatures.forEach((feature) => {
164
+ plannedFeatures.forEach(feature => {
165
165
  if (scopeMap[feature]) {
166
- recommendations.push(`For ${feature}: ${scopeMap[feature].join(", ")}`);
166
+ recommendations.push(`For ${feature}: ${scopeMap[feature].join(', ')}`);
167
167
  }
168
168
  });
169
169
  return recommendations;
170
170
  }
171
171
  generateNextSteps(plannedFeatures, appPath) {
172
172
  const steps = [
173
- "1. Create the app component configuration file and directory structure",
174
- "2. Test OAuth flow and ensure authentication works correctly",
175
- "3. Add sub-components within the app directory as needed:",
173
+ '1. Create the app component configuration file and directory structure',
174
+ '2. Test OAuth flow and ensure authentication works correctly',
175
+ '3. Add sub-components within the app directory as needed:',
176
176
  ];
177
177
  if (plannedFeatures.length > 0) {
178
- plannedFeatures.forEach((feature) => {
178
+ plannedFeatures.forEach(feature => {
179
179
  steps.push(` - ${appPath}/${feature}/`);
180
180
  });
181
181
  }
@@ -186,7 +186,7 @@ class GenerateAppComponentTool extends MCPTool {
186
186
  steps.push(` - ${appPath}/webhooks/ (for event handling)`);
187
187
  }
188
188
  steps.push("4. Ensure all sub-components can access required APIs with the app's OAuth scopes");
189
- steps.push("5. Test the complete application with all components integrated");
189
+ steps.push('5. Test the complete application with all components integrated');
190
190
  return steps;
191
191
  }
192
192
  }
@@ -1,6 +1,13 @@
1
1
  interface CLICheckResult {
2
2
  isInstalled: boolean;
3
3
  version?: string;
4
+ debugInfo?: {
5
+ command: string;
6
+ success: boolean;
7
+ output: string;
8
+ error?: string;
9
+ env?: Record<string, string | undefined>;
10
+ };
4
11
  }
5
12
  interface CommandResult {
6
13
  success: boolean;
@@ -8,6 +15,7 @@ interface CommandResult {
8
15
  error?: string;
9
16
  }
10
17
  export declare class HubSpotCLIHelper {
18
+ private execAsync;
11
19
  checkCLIInstallation(): Promise<CLICheckResult>;
12
20
  installCLI(): Promise<CommandResult>;
13
21
  runCommand(command: string): Promise<CommandResult>;
@@ -1,8 +1,22 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
1
3
  export class HubSpotCLIHelper {
4
+ execAsync = promisify(exec);
2
5
  async checkCLIInstallation() {
3
6
  try {
4
7
  // Run hs --version to check if CLI is installed
5
- const result = await this.runCommand("hs --version");
8
+ const result = await this.runCommand('hs --version');
9
+ const debugInfo = {
10
+ command: 'hs --version',
11
+ success: result.success,
12
+ output: result.output,
13
+ error: result.error,
14
+ env: {
15
+ PATH: process.env.PATH,
16
+ NODE_PATH: process.env.NODE_PATH,
17
+ npm_config_prefix: process.env.npm_config_prefix,
18
+ },
19
+ };
6
20
  if (result.success) {
7
21
  // Parse version from output (typically like "@hubspot/cli/4.0.0 darwin-x64 node-v18.17.0")
8
22
  const versionMatch = result.output.match(/@hubspot\/cli\/(\d+\.\d+\.\d+)/);
@@ -10,62 +24,84 @@ export class HubSpotCLIHelper {
10
24
  return {
11
25
  isInstalled: true,
12
26
  version: version,
27
+ debugInfo,
13
28
  };
14
29
  }
15
30
  else {
16
- return { isInstalled: false };
31
+ return {
32
+ isInstalled: false,
33
+ debugInfo,
34
+ };
17
35
  }
18
36
  }
19
37
  catch (error) {
20
- return { isInstalled: false };
38
+ return {
39
+ isInstalled: false,
40
+ debugInfo: {
41
+ command: 'hs --version',
42
+ success: false,
43
+ output: '',
44
+ error: error instanceof Error ? error.message : 'Unknown error',
45
+ env: {
46
+ PATH: process.env.PATH,
47
+ NODE_PATH: process.env.NODE_PATH,
48
+ npm_config_prefix: process.env.npm_config_prefix,
49
+ },
50
+ },
51
+ };
21
52
  }
22
53
  }
23
54
  async installCLI() {
24
55
  try {
25
- return await this.runCommand("npm install -g @hubspot/cli");
56
+ return await this.runCommand('npm install -g @hubspot/cli');
26
57
  }
27
58
  catch (error) {
28
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
59
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
29
60
  return {
30
61
  success: false,
31
- output: "",
62
+ output: '',
32
63
  error: errorMessage,
33
64
  };
34
65
  }
35
66
  }
36
67
  async runCommand(command) {
37
68
  try {
38
- // Execute the actual command using terminal
39
- const { exec } = require("child_process");
40
- const { promisify } = require("util");
41
- const execAsync = promisify(exec);
42
- const result = await execAsync(command);
69
+ // Use the same environment as the current process, including PATH
70
+ const result = await this.execAsync(command, {
71
+ env: process.env,
72
+ shell: '/bin/bash',
73
+ });
43
74
  return {
44
75
  success: true,
45
- output: result.stdout || result.stderr || "",
76
+ output: result.stdout || result.stderr || '',
46
77
  };
47
78
  }
48
79
  catch (error) {
49
80
  // Command failed (e.g., command not found, non-zero exit code)
81
+ const err = error;
50
82
  return {
51
83
  success: false,
52
- output: error.stdout || "",
53
- error: error.stderr || error.message || "Command execution failed",
84
+ output: err.stdout || '',
85
+ error: err.stderr || err.message || 'Command execution failed',
54
86
  };
55
87
  }
56
88
  }
57
- async runProjectCommand(command, projectPath = ".") {
89
+ async runProjectCommand(command, projectPath = '.') {
58
90
  try {
91
+ // Get absolute path to avoid any relative path issues
92
+ const path = await import('path');
93
+ const absoluteProjectPath = path.resolve(projectPath);
94
+ // Add debugging information about directories
95
+ const debugCommand = `echo "MCP Server CWD: $(pwd)" && echo "Project Path: ${absoluteProjectPath}" && ls -la "${absoluteProjectPath}" | head -5`;
59
96
  // Change to the project directory and run command
60
- const cdCommand = projectPath !== "." ? `cd ${projectPath} && ` : "";
61
- const fullCommand = `${cdCommand}${command}`;
97
+ const fullCommand = `cd "${absoluteProjectPath}" && ${debugCommand} && ${command}`;
62
98
  return await this.runCommand(fullCommand);
63
99
  }
64
100
  catch (error) {
65
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
101
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
66
102
  return {
67
103
  success: false,
68
- output: "",
104
+ output: '',
69
105
  error: errorMessage,
70
106
  };
71
107
  }
@@ -1,5 +1,5 @@
1
- import { MCPTool } from "mcp-framework";
2
- import { z } from "zod";
1
+ import { MCPTool } from 'mcp-framework';
2
+ import { z } from 'zod';
3
3
  interface UploadProjectInput {
4
4
  projectPath?: string;
5
5
  accountId?: string;
@@ -1,26 +1,27 @@
1
- import { MCPTool } from "mcp-framework";
2
- import { z } from "zod";
3
- import HubSpotCLIHelper from "./HubSpotCLIHelper.js";
1
+ import { MCPTool } from 'mcp-framework';
2
+ import { z } from 'zod';
3
+ import HubSpotCLIHelper from './HubSpotCLIHelper.js';
4
4
  class UploadProjectTool extends MCPTool {
5
- name = "uploadProject";
6
- description = "Uploads a developer project to HubSpot using the HubSpot CLI";
5
+ name = 'uploadProject';
6
+ description = 'Uploads a developer project to HubSpot using the HubSpot CLI';
7
7
  cliHelper = new HubSpotCLIHelper();
8
8
  schema = {
9
9
  projectPath: {
10
10
  type: z.string().optional(),
11
- description: "Path to the project directory. Defaults to current directory",
11
+ description: 'Path to the project directory. When using MCP server globally, provide the absolute path to your project (e.g., "/Users/yourname/projects/my-hubspot-project"). Defaults to "." for local usage.',
12
12
  },
13
13
  accountId: {
14
14
  type: z.string().optional(),
15
- description: "HubSpot account ID to upload to. If not provided, uses CLI default or prompts",
15
+ description: 'HubSpot account ID to upload to. If not provided, uses CLI default or prompts',
16
16
  },
17
17
  autoInstallCLI: {
18
18
  type: z.boolean().optional(),
19
- description: "Whether to automatically install HubSpot CLI if not found. Defaults to false",
19
+ description: 'Whether to automatically install HubSpot CLI if not found. Defaults to false',
20
20
  },
21
21
  };
22
22
  async execute(input) {
23
- const { projectPath = ".", accountId, autoInstallCLI = false } = input;
23
+ // Default to current directory "." which should resolve relative to where the MCP client is running
24
+ const { projectPath = '.', accountId, autoInstallCLI = false } = input;
24
25
  try {
25
26
  // Step 1: Check if HubSpot CLI is installed
26
27
  const cliCheckResult = await this.cliHelper.checkCLIInstallation();
@@ -30,20 +31,20 @@ class UploadProjectTool extends MCPTool {
30
31
  const installResult = await this.cliHelper.installCLI();
31
32
  if (!installResult.success) {
32
33
  return {
33
- status: "cli-install-failed",
34
- message: "❌ Failed to install HubSpot CLI automatically",
34
+ status: 'cli-install-failed',
35
+ message: '❌ Failed to install HubSpot CLI automatically',
35
36
  error: installResult.error,
36
37
  actions: [
37
38
  {
38
- action: "install-cli-manually",
39
- command: "npm install -g @hubspot/cli",
40
- description: "Install HubSpot CLI manually",
39
+ action: 'install-cli-manually',
40
+ command: 'npm install -g @hubspot/cli',
41
+ description: 'Install HubSpot CLI manually',
41
42
  },
42
43
  ],
43
44
  troubleshooting: [
44
- "Try installing the CLI manually: npm install -g @hubspot/cli",
45
- "Ensure you have npm installed and proper permissions",
46
- "Run the upload command again after CLI installation",
45
+ 'Try installing the CLI manually: npm install -g @hubspot/cli',
46
+ 'Ensure you have npm installed and proper permissions',
47
+ 'Run the upload command again after CLI installation',
47
48
  ],
48
49
  };
49
50
  }
@@ -51,37 +52,38 @@ class UploadProjectTool extends MCPTool {
51
52
  const recheckResult = await this.cliHelper.checkCLIInstallation();
52
53
  if (!recheckResult.isInstalled) {
53
54
  return {
54
- status: "cli-install-verification-failed",
55
- message: "❌ CLI installation completed but verification failed",
55
+ status: 'cli-install-verification-failed',
56
+ message: '❌ CLI installation completed but verification failed',
56
57
  troubleshooting: [
57
58
  "Try running 'hs --version' manually to verify installation",
58
- "Restart your terminal/shell session",
59
- "Check if the CLI was installed to the correct PATH",
59
+ 'Restart your terminal/shell session',
60
+ 'Check if the CLI was installed to the correct PATH',
60
61
  ],
61
62
  };
62
63
  }
63
64
  }
64
65
  else {
65
66
  return {
66
- status: "cli-not-found",
67
- message: "HubSpot CLI not found. Install it to upload projects.",
67
+ status: 'cli-not-found',
68
+ message: 'HubSpot CLI not found. Install it to upload projects.',
69
+ error: `Debug info: ${JSON.stringify(cliCheckResult.debugInfo, null, 2)}`,
68
70
  actions: [
69
71
  {
70
- action: "install-cli",
71
- command: "npm install -g @hubspot/cli",
72
- description: "Install HubSpot CLI globally",
72
+ action: 'install-cli',
73
+ command: 'npm install -g @hubspot/cli',
74
+ description: 'Install HubSpot CLI globally',
73
75
  },
74
76
  ],
75
77
  nextSteps: [
76
- "1. Install HubSpot CLI globally",
77
- "2. Authenticate with your HubSpot account (hs auth)",
78
- "3. Run project upload again",
78
+ '1. Install HubSpot CLI globally',
79
+ '2. Authenticate with your HubSpot account (hs auth)',
80
+ '3. Run project upload again',
79
81
  ],
80
82
  };
81
83
  }
82
84
  }
83
85
  // Step 2: Build the upload command
84
- let uploadCommand = "hs project upload";
86
+ let uploadCommand = 'hs project upload';
85
87
  if (accountId) {
86
88
  uploadCommand += ` --account=${accountId}`;
87
89
  }
@@ -91,56 +93,71 @@ class UploadProjectTool extends MCPTool {
91
93
  // Parse successful upload output
92
94
  const output = uploadResult.output;
93
95
  return {
94
- status: "success",
95
- message: "✅ Project uploaded successfully to HubSpot!",
96
+ status: 'success',
97
+ message: '✅ Project uploaded successfully to HubSpot!',
96
98
  cliVersion: cliCheckResult.version,
97
99
  uploadOutput: output,
98
100
  accountId: accountId,
99
101
  projectPath: projectPath,
100
102
  nextSteps: [
101
- "1. Check your HubSpot account to verify the project appears",
102
- "2. Test your components in the HubSpot environment",
103
- "3. Monitor for any runtime errors or issues",
103
+ '1. Check your HubSpot account to verify the project appears',
104
+ '2. Test your components in the HubSpot environment',
105
+ '3. Monitor for any runtime errors or issues',
104
106
  "4. Use 'hs project logs' to view application logs if needed",
105
107
  ],
106
108
  };
107
109
  }
108
110
  else {
109
111
  // Parse upload failure
110
- const errorOutput = uploadResult.output || uploadResult.error || "Upload failed";
112
+ const errorOutput = uploadResult.output || uploadResult.error || 'Upload failed';
113
+ // Check if this looks like a path issue with global MCP server
114
+ const isPathIssue = errorOutput.includes('Unable to locate a project configuration file') || errorOutput.includes('MCP Server CWD: /');
111
115
  return {
112
- status: "upload-failed",
113
- message: "❌ Project upload failed",
116
+ status: 'upload-failed',
117
+ message: '❌ Project upload failed',
114
118
  cliVersion: cliCheckResult.version,
115
119
  uploadOutput: errorOutput,
116
120
  error: uploadResult.error,
117
121
  troubleshooting: [
118
122
  "Ensure you're authenticated: run 'hs auth' to login",
119
- "Verify your account has permission to upload projects",
123
+ 'Verify your account has permission to upload projects',
120
124
  "Check that your project is valid: run 'hs project validate' first",
121
- "Ensure you have network connectivity to HubSpot",
122
- "Try uploading to a different account if you have access to multiple",
125
+ 'Ensure you have network connectivity to HubSpot',
126
+ 'Try uploading to a different account if you have access to multiple',
127
+ `Working directory was: ${projectPath}`,
128
+ ...(isPathIssue
129
+ ? [
130
+ '⚠️ PATH ISSUE DETECTED: You may be using the global MCP server',
131
+ 'When using global MCP server, provide the absolute path to your project:',
132
+ 'Example: {"projectPath": "/Users/yourname/projects/my-hubspot-project"}',
133
+ 'Or consider running the MCP server locally from your project directory',
134
+ ]
135
+ : []),
123
136
  ],
124
137
  nextSteps: [
125
- "1. Check authentication status: hs auth info",
126
- "2. Validate project first: hs project validate",
127
- "3. Review the error output above for specific issues",
128
- "4. Try the upload command again after fixing issues",
138
+ '1. Check authentication status: hs auth info',
139
+ '2. Validate project first: hs project validate',
140
+ ...(isPathIssue
141
+ ? ['3. Provide absolute projectPath if using global MCP server']
142
+ : []),
143
+ '4. Review the error output above for specific issues',
144
+ '5. Try the upload command again after fixing issues',
129
145
  ],
130
146
  };
131
147
  }
132
148
  }
133
149
  catch (error) {
134
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
150
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
135
151
  return {
136
- status: "error",
137
- message: "An error occurred during project upload",
152
+ status: 'error',
153
+ message: 'An error occurred during project upload',
138
154
  error: errorMessage,
139
155
  troubleshooting: [
140
156
  "Ensure you're in a valid developer project directory",
141
- "Check that hsproject.json exists in the project root",
142
- "Verify HubSpot CLI is properly installed and accessible",
157
+ 'Check that hsproject.json exists in the project root',
158
+ 'Verify HubSpot CLI is properly installed and accessible',
143
159
  "Try running 'hs project upload' manually to see detailed output",
160
+ `Attempted project path: ${projectPath}`,
144
161
  ],
145
162
  };
146
163
  }
@@ -1,5 +1,5 @@
1
- import { MCPTool } from "mcp-framework";
2
- import { z } from "zod";
1
+ import { MCPTool } from 'mcp-framework';
2
+ import { z } from 'zod';
3
3
  interface ValidateProjectInput {
4
4
  autoFix?: boolean;
5
5
  projectPath?: string;
@@ -13,7 +13,7 @@ interface ValidationResponse {
13
13
  errorAnalysis?: Array<{
14
14
  category: string;
15
15
  issue: string;
16
- severity: "error" | "warning";
16
+ severity: 'error' | 'warning';
17
17
  component?: string;
18
18
  file?: string;
19
19
  }>;
@@ -1,40 +1,42 @@
1
- import { MCPTool } from "mcp-framework";
2
- import { z } from "zod";
3
- import HubSpotCLIHelper from "./HubSpotCLIHelper.js";
1
+ import { MCPTool } from 'mcp-framework';
2
+ import { z } from 'zod';
3
+ import HubSpotCLIHelper from './HubSpotCLIHelper.js';
4
4
  class ValidateProjectTool extends MCPTool {
5
- name = "validateProject";
6
- description = "Validates a developer project using the HubSpot CLI and provides guidance for fixing any validation errors";
5
+ name = 'validateProject';
6
+ description = 'Validates a developer project using the HubSpot CLI and provides guidance for fixing any validation errors';
7
7
  cliHelper = new HubSpotCLIHelper();
8
8
  schema = {
9
9
  autoFix: {
10
10
  type: z.boolean().optional(),
11
- description: "Whether to attempt automatic fixes for common validation errors. Defaults to false",
11
+ description: 'Whether to attempt automatic fixes for common validation errors. Defaults to false',
12
12
  },
13
13
  projectPath: {
14
14
  type: z.string().optional(),
15
- description: "Path to the project directory. Defaults to current directory",
15
+ description: 'Path to the project directory. When using MCP server globally, provide the absolute path to your project (e.g., "/Users/yourname/projects/my-hubspot-project"). Defaults to "." for local usage.',
16
16
  },
17
17
  };
18
18
  async execute(input) {
19
- const { autoFix = false, projectPath = "." } = input;
19
+ // Default to current directory "." which should resolve relative to where the MCP client is running
20
+ const { autoFix = false, projectPath = '.' } = input;
20
21
  try {
21
22
  // Step 1: Check if HubSpot CLI is installed
22
23
  const cliCheckResult = await this.cliHelper.checkCLIInstallation();
23
24
  if (!cliCheckResult.isInstalled) {
24
25
  return {
25
- status: "cli-not-found",
26
- message: "HubSpot CLI not found. Installing...",
26
+ status: 'cli-not-found',
27
+ error: `Debug info: ${JSON.stringify(cliCheckResult.debugInfo, null, 2)}`,
28
+ message: 'HubSpot CLI not found. Installing...',
27
29
  actions: [
28
30
  {
29
- action: "install-cli",
30
- command: "npm install -g @hubspot/cli",
31
- description: "Installing HubSpot CLI globally",
31
+ action: 'install-cli',
32
+ command: 'npm install -g @hubspot/cli',
33
+ description: 'Installing HubSpot CLI globally',
32
34
  },
33
35
  ],
34
36
  nextSteps: [
35
- "1. Install HubSpot CLI globally",
36
- "2. Run project validation again",
37
- "3. Follow any additional guidance for fixing validation errors",
37
+ '1. Install HubSpot CLI globally',
38
+ '2. Run project validation again',
39
+ '3. Follow any additional guidance for fixing validation errors',
38
40
  ],
39
41
  };
40
42
  }
@@ -42,18 +44,18 @@ class ValidateProjectTool extends MCPTool {
42
44
  const validationResult = await this.runProjectValidation(projectPath);
43
45
  if (validationResult.isValid) {
44
46
  return {
45
- status: "valid",
46
- message: "✅ Project validation passed! Your developer project is properly configured.",
47
+ status: 'valid',
48
+ message: '✅ Project validation passed! Your developer project is properly configured.',
47
49
  cliVersion: cliCheckResult.version,
48
50
  validationOutput: validationResult.output,
49
- projectStructure: "All components and configurations follow platform conventions",
51
+ projectStructure: 'All components and configurations follow platform conventions',
50
52
  };
51
53
  }
52
54
  // Step 3: Parse validation errors and provide guidance
53
55
  const errorAnalysis = this.analyzeValidationErrors(validationResult.errors);
54
56
  const suggestions = this.generateFixSuggestions(errorAnalysis);
55
57
  const response = {
56
- status: "validation-failed",
58
+ status: 'validation-failed',
57
59
  message: "❌ Project validation failed. Here's how to fix the issues:",
58
60
  cliVersion: cliCheckResult.version,
59
61
  validationOutput: validationResult.output,
@@ -68,36 +70,49 @@ class ValidateProjectTool extends MCPTool {
68
70
  return response;
69
71
  }
70
72
  catch (error) {
71
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
73
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
72
74
  return {
73
- status: "error",
74
- message: "An error occurred during project validation",
75
+ status: 'error',
76
+ message: 'An error occurred during project validation',
75
77
  error: errorMessage,
76
78
  troubleshooting: [
77
79
  "Ensure you're in a valid developer project directory",
78
- "Check that hsproject.json exists in the project root",
79
- "Verify HubSpot CLI is properly installed and accessible",
80
+ 'Check that hsproject.json exists in the project root',
81
+ 'Verify HubSpot CLI is properly installed and accessible',
80
82
  "Try running 'hs project validate' manually to see detailed output",
83
+ `Attempted project path: ${projectPath}`,
81
84
  ],
82
85
  };
83
86
  }
84
87
  }
85
88
  async runProjectValidation(projectPath) {
86
89
  try {
87
- const result = await this.cliHelper.runProjectCommand("hs project validate", projectPath);
90
+ const result = await this.cliHelper.runProjectCommand('hs project validate', projectPath);
88
91
  if (result.success) {
89
92
  // Parse the validation output to determine if it passed
90
93
  const output = result.output;
91
94
  // Check for validation success indicators
92
- const isValid = output.includes("") ||
93
- output.includes("Project validation passed") ||
94
- output.includes("valid") ||
95
- !output.includes("Project validation failed:");
95
+ const isValid = output.includes('') ||
96
+ output.includes('Project validation passed') ||
97
+ output.includes('valid') ||
98
+ !output.includes('Project validation failed:');
96
99
  // Parse structured validation errors if present
97
100
  const errors = [];
98
- if (!isValid && output.includes("Project validation failed:")) {
101
+ if (!isValid && output.includes('Project validation failed:')) {
99
102
  errors.push(...this.parseValidationErrors(output));
100
103
  }
104
+ // Add debugging information about directory context
105
+ if (result.output.includes('Unable to locate a project configuration file')) {
106
+ errors.push(`Working directory was: ${projectPath}`);
107
+ errors.push("Make sure you're running this from a directory containing hsproject.json");
108
+ // Check if this looks like a global MCP server path issue
109
+ if (result.output.includes('MCP Server CWD: /')) {
110
+ errors.push('⚠️ PATH ISSUE DETECTED: You may be using the global MCP server');
111
+ errors.push('When using global MCP server, provide the absolute path to your project:');
112
+ errors.push('Example: {"projectPath": "/Users/yourname/projects/my-hubspot-project"}');
113
+ errors.push('Or consider running the MCP server locally from your project directory');
114
+ }
115
+ }
101
116
  return {
102
117
  isValid: isValid,
103
118
  output: output,
@@ -106,16 +121,22 @@ class ValidateProjectTool extends MCPTool {
106
121
  }
107
122
  else {
108
123
  // Command failed to run
109
- const errorMessage = result.error || "Failed to run project validation";
124
+ const errorMessage = result.error || 'Failed to run project validation';
125
+ const errors = [errorMessage];
126
+ // Add debugging information about directory context
127
+ if (result.output.includes('Unable to locate a project configuration file')) {
128
+ errors.push(`Working directory was: ${projectPath}`);
129
+ errors.push("Make sure you're running this from a directory containing hsproject.json");
130
+ }
110
131
  return {
111
132
  isValid: false,
112
- output: result.output || "",
113
- errors: [errorMessage],
133
+ output: result.output || '',
134
+ errors: errors,
114
135
  };
115
136
  }
116
137
  }
117
138
  catch (error) {
118
- const errorMessage = error instanceof Error ? error.message : "Unknown validation error";
139
+ const errorMessage = error instanceof Error ? error.message : 'Unknown validation error';
119
140
  throw new Error(`Failed to run project validation: ${errorMessage}`);
120
141
  }
121
142
  }
@@ -125,8 +146,8 @@ class ValidateProjectTool extends MCPTool {
125
146
  }
126
147
  parseValidationErrors(output) {
127
148
  const errors = [];
128
- const lines = output.split("\n");
129
- let currentFile = "";
149
+ const lines = output.split('\n');
150
+ let currentFile = '';
130
151
  let inErrorSection = false;
131
152
  for (const line of lines) {
132
153
  // Look for file error headers like "Encountered the following errors for src/app/file.json:"
@@ -138,7 +159,7 @@ class ValidateProjectTool extends MCPTool {
138
159
  }
139
160
  // Parse individual error lines (start with tab and dash)
140
161
  if (inErrorSection && line.match(/^\s*-\s+/)) {
141
- const errorText = line.replace(/^\s*-\s+/, "").trim();
162
+ const errorText = line.replace(/^\s*-\s+/, '').trim();
142
163
  if (errorText) {
143
164
  errors.push(`${currentFile}: ${errorText}`);
144
165
  }
@@ -146,7 +167,7 @@ class ValidateProjectTool extends MCPTool {
146
167
  }
147
168
  // Handle multi-line errors with additional indentation
148
169
  if (inErrorSection && line.match(/^\s{2,}-\s+/)) {
149
- const errorText = line.replace(/^\s*-\s+/, "").trim();
170
+ const errorText = line.replace(/^\s*-\s+/, '').trim();
150
171
  if (errorText && errors.length > 0) {
151
172
  // Append to the last error as additional context
152
173
  errors[errors.length - 1] += `\n ${errorText}`;
@@ -154,10 +175,10 @@ class ValidateProjectTool extends MCPTool {
154
175
  continue;
155
176
  }
156
177
  // Reset when we hit an empty line or new section
157
- if (line.trim() === "" ||
158
- (!line.startsWith("\t") && !line.startsWith(" "))) {
178
+ if (line.trim() === '' ||
179
+ (!line.startsWith('\t') && !line.startsWith(' '))) {
159
180
  inErrorSection = false;
160
- currentFile = "";
181
+ currentFile = '';
161
182
  }
162
183
  }
163
184
  return errors;
@@ -165,37 +186,37 @@ class ValidateProjectTool extends MCPTool {
165
186
  analyzeValidationErrors(errors) {
166
187
  const analysis = [];
167
188
  for (const error of errors) {
168
- const filePath = error.split(":")[0];
169
- const errorMessage = error.substring(error.indexOf(":") + 1).trim();
170
- let category = "unknown";
171
- let component = "unknown";
172
- let severity = "error";
189
+ const filePath = error.split(':')[0];
190
+ const errorMessage = error.substring(error.indexOf(':') + 1).trim();
191
+ let category = 'unknown';
192
+ let component = 'unknown';
193
+ let severity = 'error';
173
194
  // Extract file information
174
- if (filePath.includes("hsproject.json")) {
175
- category = "project-config";
176
- component = "project";
195
+ if (filePath.includes('hsproject.json')) {
196
+ category = 'project-config';
197
+ component = 'project';
177
198
  }
178
- else if (filePath.includes("-hsmeta.json")) {
179
- category = "component-config";
199
+ else if (filePath.includes('-hsmeta.json')) {
200
+ category = 'component-config';
180
201
  component = this.extractComponentFromPath(filePath);
181
202
  }
182
- else if (filePath.includes("package.json")) {
183
- category = "dependencies";
203
+ else if (filePath.includes('package.json')) {
204
+ category = 'dependencies';
184
205
  component = this.extractComponentFromPath(filePath);
185
- severity = "warning";
206
+ severity = 'warning';
186
207
  }
187
208
  // Analyze error message content
188
- if (errorMessage.includes("Invalid JSON")) {
189
- category = "json-syntax";
209
+ if (errorMessage.includes('Invalid JSON')) {
210
+ category = 'json-syntax';
190
211
  }
191
- else if (errorMessage.includes("Missing required field")) {
192
- category = "required-field";
212
+ else if (errorMessage.includes('Missing required field')) {
213
+ category = 'required-field';
193
214
  }
194
- else if (errorMessage.includes("must NOT have additional properties")) {
195
- category = "schema-validation";
215
+ else if (errorMessage.includes('must NOT have additional properties')) {
216
+ category = 'schema-validation';
196
217
  }
197
- else if (errorMessage.includes("entrypoint")) {
198
- category = "entrypoint";
218
+ else if (errorMessage.includes('entrypoint')) {
219
+ category = 'entrypoint';
199
220
  }
200
221
  analysis.push({
201
222
  category: category,
@@ -209,46 +230,46 @@ class ValidateProjectTool extends MCPTool {
209
230
  }
210
231
  extractComponentFromPath(filePath) {
211
232
  // Extract component type from file path like "src/app/cards/my-card-hsmeta.json"
212
- if (filePath.includes("/app/")) {
213
- const pathParts = filePath.split("/app/")[1].split("/");
233
+ if (filePath.includes('/app/')) {
234
+ const pathParts = filePath.split('/app/')[1].split('/');
214
235
  if (pathParts.length > 1) {
215
236
  return pathParts[0]; // e.g., "cards", "functions", etc.
216
237
  }
217
- return "app";
238
+ return 'app';
218
239
  }
219
240
  // Handle top-level app component files
220
- if (filePath.includes("-hsmeta.json")) {
221
- return "app";
241
+ if (filePath.includes('-hsmeta.json')) {
242
+ return 'app';
222
243
  }
223
- return "unknown";
244
+ return 'unknown';
224
245
  }
225
246
  generateFixSuggestions(errorAnalysis) {
226
247
  const suggestions = [];
227
248
  for (const error of errorAnalysis) {
228
249
  switch (error.category) {
229
- case "project-config":
230
- suggestions.push("Fix hsproject.json: Ensure it contains 'name', 'srcDir', and 'platformVersion' fields", "Use the generateProjectConfig tool to create a valid hsproject.json file");
250
+ case 'project-config':
251
+ suggestions.push("Fix hsproject.json: Ensure it contains 'name', 'srcDir', and 'platformVersion' fields", 'Use the generateProjectConfig tool to create a valid hsproject.json file');
231
252
  break;
232
- case "component-config":
233
- suggestions.push(`Fix ${error.component} component: Check the *-hsmeta.json file structure`, "Ensure the component follows the envelope pattern: uid, type, config", `Use the appropriate generate component tool for ${error.component} type`);
253
+ case 'component-config':
254
+ suggestions.push(`Fix ${error.component} component: Check the *-hsmeta.json file structure`, 'Ensure the component follows the envelope pattern: uid, type, config', `Use the appropriate generate component tool for ${error.component} type`);
234
255
  break;
235
- case "json-syntax":
236
- suggestions.push(`Fix JSON syntax error in ${error.file}`, "Check for missing commas, brackets, or quotes in the JSON file", "Use a JSON validator to identify the specific syntax issue");
256
+ case 'json-syntax':
257
+ suggestions.push(`Fix JSON syntax error in ${error.file}`, 'Check for missing commas, brackets, or quotes in the JSON file', 'Use a JSON validator to identify the specific syntax issue');
237
258
  break;
238
- case "required-field":
239
- suggestions.push(`Add missing required field: ${this.extractFieldFromError(error.issue)}`, `Update ${error.file} to include all required fields`, "Refer to the platform documentation for required field specifications");
259
+ case 'required-field':
260
+ suggestions.push(`Add missing required field: ${this.extractFieldFromError(error.issue)}`, `Update ${error.file} to include all required fields`, 'Refer to the platform documentation for required field specifications');
240
261
  break;
241
- case "schema-validation":
242
- suggestions.push(`Fix schema validation error in ${error.file}`, "Remove additional properties that are not allowed", `Check ${error.component} component schema requirements`, "Ensure all field types match the expected schema");
262
+ case 'schema-validation':
263
+ suggestions.push(`Fix schema validation error in ${error.file}`, 'Remove additional properties that are not allowed', `Check ${error.component} component schema requirements`, 'Ensure all field types match the expected schema');
243
264
  break;
244
- case "dependencies":
245
- suggestions.push("Fix package.json: Ensure React and @hubspot/ui-extensions dependencies are present", "Run 'npm install' to install missing dependencies", "Check that package.json is in the correct component directory");
265
+ case 'dependencies':
266
+ suggestions.push('Fix package.json: Ensure React and @hubspot/ui-extensions dependencies are present', "Run 'npm install' to install missing dependencies", 'Check that package.json is in the correct component directory');
246
267
  break;
247
- case "entrypoint":
248
- suggestions.push("Fix component entrypoint: Ensure the React file exists at the specified path", "Check that the entrypoint path in *-hsmeta.json matches the actual file location", "Verify the React component exports properly");
268
+ case 'entrypoint':
269
+ suggestions.push('Fix component entrypoint: Ensure the React file exists at the specified path', 'Check that the entrypoint path in *-hsmeta.json matches the actual file location', 'Verify the React component exports properly');
249
270
  break;
250
271
  default:
251
- suggestions.push(`Review error in ${error.file || "project"}: ${error.issue}`);
272
+ suggestions.push(`Review error in ${error.file || 'project'}: ${error.issue}`);
252
273
  }
253
274
  }
254
275
  return [...new Set(suggestions)]; // Remove duplicates
@@ -256,53 +277,53 @@ class ValidateProjectTool extends MCPTool {
256
277
  extractFieldFromError(errorMessage) {
257
278
  // Extract field name from messages like "Missing required field: config.description"
258
279
  const match = errorMessage.match(/Missing required field:\s*(.+)/);
259
- return match ? match[1] : "unknown field";
280
+ return match ? match[1] : 'unknown field';
260
281
  }
261
282
  generateNextSteps(errorAnalysis, autoFix) {
262
283
  const steps = [];
263
284
  if (autoFix) {
264
- steps.push("1. Review auto-fix actions and apply them");
285
+ steps.push('1. Review auto-fix actions and apply them');
265
286
  }
266
287
  else {
267
- steps.push("1. Review the validation errors and suggestions above");
288
+ steps.push('1. Review the validation errors and suggestions above');
268
289
  }
269
- const hasProjectConfig = errorAnalysis.some((e) => e.category === "project-config");
270
- const hasComponentIssues = errorAnalysis.some((e) => e.category === "component-config");
271
- const hasDependencyIssues = errorAnalysis.some((e) => e.category === "dependencies");
290
+ const hasProjectConfig = errorAnalysis.some(e => e.category === 'project-config');
291
+ const hasComponentIssues = errorAnalysis.some(e => e.category === 'component-config');
292
+ const hasDependencyIssues = errorAnalysis.some(e => e.category === 'dependencies');
272
293
  if (hasProjectConfig) {
273
- steps.push("2. Fix hsproject.json configuration issues first");
294
+ steps.push('2. Fix hsproject.json configuration issues first');
274
295
  }
275
296
  if (hasComponentIssues) {
276
- steps.push("3. Fix component configuration issues");
297
+ steps.push('3. Fix component configuration issues');
277
298
  }
278
299
  if (hasDependencyIssues) {
279
- steps.push("4. Install missing dependencies");
300
+ steps.push('4. Install missing dependencies');
280
301
  }
281
- steps.push("5. Run validation again to verify fixes");
282
- steps.push("6. Test your components work correctly in development");
302
+ steps.push('5. Run validation again to verify fixes');
303
+ steps.push('6. Test your components work correctly in development');
283
304
  return steps;
284
305
  }
285
306
  generateAutoFixActions(errorAnalysis) {
286
307
  const actions = [];
287
308
  for (const error of errorAnalysis) {
288
309
  switch (error.category) {
289
- case "project-config":
310
+ case 'project-config':
290
311
  actions.push({
291
- action: "fix-project-config",
292
- description: "Generate valid hsproject.json configuration",
293
- tool: "generateProjectConfig",
312
+ action: 'fix-project-config',
313
+ description: 'Generate valid hsproject.json configuration',
314
+ tool: 'generateProjectConfig',
294
315
  });
295
316
  break;
296
- case "dependencies":
317
+ case 'dependencies':
297
318
  actions.push({
298
- action: "install-dependencies",
299
- description: "Install missing npm dependencies",
300
- command: "npm install",
319
+ action: 'install-dependencies',
320
+ description: 'Install missing npm dependencies',
321
+ command: 'npm install',
301
322
  });
302
323
  break;
303
- case "component-config":
324
+ case 'component-config':
304
325
  actions.push({
305
- action: "fix-component-config",
326
+ action: 'fix-component-config',
306
327
  description: `Fix ${error.component} component configuration`,
307
328
  tool: `generate${error.component}Component`,
308
329
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "7.7.0-experimental.0",
3
+ "version": "7.7.0-experimental.2",
4
4
  "description": "The official CLI for developing on HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/HubSpot/hubspot-cli",