@ghl-ai/aw 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/mcp.mjs +70 -116
  2. package/package.json +1 -1
package/mcp.mjs CHANGED
@@ -1,4 +1,5 @@
1
- // mcp.mjs — MCP config generation for all 3 IDEs (init only, never overwrites)
1
+ // mcp.mjs — MCP config generation for Claude Code + Cursor (global ~/ configs)
2
+ // Uses native Streamable HTTP — no bridge process needed.
2
3
 
3
4
  import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
4
5
  import { join, resolve } from 'node:path';
@@ -6,149 +7,102 @@ import { homedir } from 'node:os';
6
7
  import * as fmt from './fmt.mjs';
7
8
 
8
9
  const HOME = homedir();
10
+ const DEFAULT_MCP_URL = 'https://staging.services.leadconnectorhq.com/agentic-workspace/mcp';
9
11
 
10
12
  /**
11
13
  * Auto-detect MCP server paths.
12
14
  */
13
15
  function detectPaths() {
14
- const ghlAiBridge = join(HOME, '.claude', 'ghl-ai-bridge.mjs');
15
16
  const gitJenkinsCandidates = [
16
17
  join(HOME, 'Documents', 'GitHub', 'git-jenkins-mcp', 'dist', 'index.js'),
17
18
  resolve('..', 'git-jenkins-mcp', 'dist', 'index.js'),
18
19
  ];
19
20
 
20
21
  const gitJenkinsPath = gitJenkinsCandidates.find(p => existsSync(p)) || null;
21
- const ghlMcpUrl = process.env.GHL_MCP_URL || 'https://ghl-ai-staging.highlevel-staging.com';
22
+ const ghlMcpUrl = process.env.GHL_MCP_URL || DEFAULT_MCP_URL;
22
23
 
23
- return {
24
- ghlAiBridge: existsSync(ghlAiBridge) ? ghlAiBridge : null,
25
- gitJenkinsPath,
26
- ghlMcpUrl,
27
- };
24
+ return { gitJenkinsPath, ghlMcpUrl };
28
25
  }
29
26
 
30
27
  /**
31
- * Setup MCP configs for all IDEs. Only writes if file doesn't exist.
32
- * Returns list of file paths that were actually created.
28
+ * Setup MCP configs globally for Claude Code and Cursor.
29
+ * Merges ghl-ai server into existing configs without overwriting other servers.
30
+ * Returns list of file paths that were created or updated.
33
31
  */
34
32
  export function setupMcp(cwd, namespace) {
35
33
  const paths = detectPaths();
36
- const createdFiles = [];
34
+ const updatedFiles = [];
37
35
 
38
- const attempt = (filePath, contentFn, append = false) => {
39
- if (writeIfMissing(filePath, contentFn, append)) {
40
- createdFiles.push(filePath);
41
- }
42
- };
43
-
44
- // .claude/settings.local.json
45
- attempt(join(cwd, '.claude', 'settings.local.json'), () => {
46
- const servers = {};
47
- if (paths.ghlAiBridge) {
48
- servers['ghl-ai'] = {
49
- command: 'node',
50
- args: [paths.ghlAiBridge],
51
- env: { GHL_MCP_URL: paths.ghlMcpUrl, TEAM_NAME: namespace || '' },
52
- };
53
- }
54
- if (paths.gitJenkinsPath) {
55
- servers['git-jenkins'] = { command: 'node', args: [paths.gitJenkinsPath] };
56
- }
57
- if (Object.keys(servers).length === 0) return null;
58
- return JSON.stringify({ mcpServers: servers }, null, 2) + '\n';
59
- });
60
-
61
- // .cursor/mcp.json
62
- attempt(join(cwd, '.cursor', 'mcp.json'), () => {
63
- const servers = {};
64
- if (paths.ghlAiBridge) {
65
- servers['ghl-ai'] = {
66
- command: 'node',
67
- args: [paths.ghlAiBridge],
68
- env: { GHL_MCP_URL: paths.ghlMcpUrl, TEAM_NAME: namespace || '' },
69
- };
70
- }
71
- if (paths.gitJenkinsPath) {
72
- servers['git-jenkins'] = { command: 'node', args: [paths.gitJenkinsPath] };
73
- }
74
- if (Object.keys(servers).length === 0) return null;
75
- return JSON.stringify({ mcpServers: servers }, null, 2) + '\n';
76
- });
77
-
78
- // .codex/config.toml — append MCP servers
79
- const codexConfig = join(cwd, '.codex', 'config.toml');
80
- if (!hasMcpSection(codexConfig)) {
81
- attempt(codexConfig, () => {
82
- const lines = [];
83
- if (paths.ghlAiBridge) {
84
- lines.push(
85
- '[mcp_servers.ghl-ai]',
86
- 'command = "node"',
87
- `args = ["${paths.ghlAiBridge}"]`,
88
- '',
89
- '[mcp_servers.ghl-ai.env]',
90
- `GHL_MCP_URL = "${paths.ghlMcpUrl}"`,
91
- `TEAM_NAME = "${namespace || ''}"`,
92
- '',
93
- );
94
- }
95
- if (paths.gitJenkinsPath) {
96
- lines.push(
97
- '[mcp_servers.git-jenkins]',
98
- 'command = "node"',
99
- `args = ["${paths.gitJenkinsPath}"]`,
100
- '',
101
- );
102
- }
103
- if (lines.length === 0) return null;
104
- return lines.join('\n');
105
- }, true /* append mode */);
106
- }
36
+ const mcpUrl = paths.ghlMcpUrl;
107
37
 
108
- // mcp.json at project root (Claude Code auto-discovery)
109
- attempt(join(cwd, 'mcp.json'), () => {
110
- const servers = {};
111
- if (paths.ghlAiBridge) {
112
- servers['ghl-ai'] = {
113
- command: 'node',
114
- args: [paths.ghlAiBridge],
115
- env: { GHL_MCP_URL: paths.ghlMcpUrl, TEAM_NAME: namespace || '' },
116
- };
117
- }
118
- if (Object.keys(servers).length === 0) return null;
119
- return JSON.stringify({ mcpServers: servers }, null, 2) + '\n';
120
- });
38
+ const ghlAiServer = { type: 'url', url: mcpUrl };
39
+ const gitJenkinsServer = paths.gitJenkinsPath
40
+ ? { command: 'node', args: [paths.gitJenkinsPath] }
41
+ : null;
121
42
 
122
- if (createdFiles.length > 0) {
123
- fmt.logSuccess(`Created ${createdFiles.length} MCP config${createdFiles.length > 1 ? 's' : ''}`);
43
+ // ── Claude Code: ~/.claude/settings.json ──
44
+ const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
45
+ if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai', ghlAiServer)) {
46
+ updatedFiles.push(claudeSettingsPath);
47
+ }
48
+ if (gitJenkinsServer && mergeJsonMcpServer(claudeSettingsPath, 'git-jenkins', gitJenkinsServer)) {
49
+ updatedFiles.push(claudeSettingsPath);
124
50
  }
125
51
 
126
- const warnings = [];
127
- if (!paths.ghlAiBridge) warnings.push('ghl-ai bridge not found at ~/.claude/ghl-ai-bridge.mjs');
128
- if (!paths.gitJenkinsPath) warnings.push('git-jenkins MCP not found');
129
- for (const w of warnings) {
130
- fmt.logWarn(w);
52
+ // ── Cursor: ~/.cursor/mcp.json ──
53
+ const cursorMcpPath = join(HOME, '.cursor', 'mcp.json');
54
+ if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServer)) {
55
+ updatedFiles.push(cursorMcpPath);
56
+ }
57
+ if (gitJenkinsServer && mergeJsonMcpServer(cursorMcpPath, 'git-jenkins', gitJenkinsServer)) {
58
+ updatedFiles.push(cursorMcpPath);
131
59
  }
132
60
 
133
- return createdFiles;
134
- }
61
+ // Deduplicate (claude settings may be added twice if both servers written)
62
+ const unique = [...new Set(updatedFiles)];
135
63
 
136
- function writeIfMissing(filePath, contentFn, append = false) {
137
- if (!append && existsSync(filePath)) return false;
138
- const content = contentFn();
139
- if (!content) return false;
140
- mkdirSync(join(filePath, '..'), { recursive: true });
141
- if (append && existsSync(filePath)) {
142
- const existing = readFileSync(filePath, 'utf8');
143
- writeFileSync(filePath, existing + '\n' + content);
64
+ if (unique.length > 0) {
65
+ fmt.logSuccess(`MCP configured in ${unique.map(f => fmt.chalk.cyan(f)).join(', ')}`);
144
66
  } else {
145
- writeFileSync(filePath, content);
67
+ fmt.logInfo('MCP servers already configured — no changes needed');
146
68
  }
147
- return true;
69
+
70
+ if (!paths.gitJenkinsPath) {
71
+ fmt.logWarn('git-jenkins MCP not found — skipped');
72
+ }
73
+
74
+ return unique;
148
75
  }
149
76
 
150
- function hasMcpSection(filePath) {
151
- if (!existsSync(filePath)) return false;
152
- const content = readFileSync(filePath, 'utf8');
153
- return content.includes('[mcp_servers');
77
+ /**
78
+ * Merge an MCP server entry into a JSON config file.
79
+ * Creates the file if it doesn't exist.
80
+ * Returns true if the file was modified.
81
+ */
82
+ function mergeJsonMcpServer(filePath, serverName, serverConfig) {
83
+ let config = {};
84
+
85
+ if (existsSync(filePath)) {
86
+ try {
87
+ config = JSON.parse(readFileSync(filePath, 'utf8'));
88
+ } catch {
89
+ config = {};
90
+ }
91
+ }
92
+
93
+ if (!config.mcpServers) {
94
+ config.mcpServers = {};
95
+ }
96
+
97
+ // Check if already configured with same URL
98
+ const existing = config.mcpServers[serverName];
99
+ if (existing && JSON.stringify(existing) === JSON.stringify(serverConfig)) {
100
+ return false;
101
+ }
102
+
103
+ config.mcpServers[serverName] = serverConfig;
104
+
105
+ mkdirSync(join(filePath, '..'), { recursive: true });
106
+ writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
107
+ return true;
154
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {