harmony-mcp 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,10 +98,13 @@ harmony-mcp init # Initialize for AI agents (interactive)
98
98
  harmony-mcp init --all # Configure all supported agents
99
99
  harmony-mcp init --detect # Auto-detect and configure installed agents
100
100
  harmony-mcp init --agent X Y # Configure specific agents
101
- harmony-mcp status # Show current config
101
+ harmony-mcp init -w WS -p PROJ # Initialize with local workspace/project context
102
+ harmony-mcp status # Show current config (global and local)
102
103
  harmony-mcp reset # Clear configuration
103
- harmony-mcp set-workspace ID # Set active workspace
104
- harmony-mcp set-project ID # Set active project
104
+ harmony-mcp set-workspace ID # Set active workspace (global)
105
+ harmony-mcp set-workspace ID -l # Set active workspace (local project)
106
+ harmony-mcp set-project ID # Set active project (global)
107
+ harmony-mcp set-project ID -l # Set active project (local project)
105
108
  harmony-mcp serve # Start MCP server
106
109
  ```
107
110
 
@@ -233,7 +236,9 @@ curl -X GET "https://gethmy.com/api/workspaces" \
233
236
 
234
237
  ## Configuration
235
238
 
236
- Your configuration is stored in `~/.harmony-mcp/config.json`:
239
+ ### Global Configuration
240
+
241
+ Your global configuration is stored in `~/.harmony-mcp/config.json`:
237
242
 
238
243
  ```json
239
244
  {
@@ -244,6 +249,66 @@ Your configuration is stored in `~/.harmony-mcp/config.json`:
244
249
  }
245
250
  ```
246
251
 
252
+ ### Local Project Configuration
253
+
254
+ For multi-project workflows, you can set project-specific context using local configuration. This is stored in `.harmony-mcp.json` in your project root:
255
+
256
+ ```json
257
+ {
258
+ "workspaceId": "uuid-here",
259
+ "projectId": "uuid-here"
260
+ }
261
+ ```
262
+
263
+ **Priority**: Local config overrides global config for workspace and project context.
264
+
265
+ **Security**: API key is never stored locally (only in global config) to prevent accidental commits.
266
+
267
+ #### Setting Local Context
268
+
269
+ ```bash
270
+ # During initialization
271
+ harmony-mcp init --workspace <ws-id> --project <proj-id>
272
+
273
+ # Or separately
274
+ harmony-mcp set-workspace <id> --local
275
+ harmony-mcp set-project <id> --local
276
+ ```
277
+
278
+ #### Viewing Configuration
279
+
280
+ ```bash
281
+ harmony-mcp status
282
+ ```
283
+
284
+ Example output:
285
+
286
+ ```
287
+ Status: Configured
288
+ API Key: hmy_abc1...
289
+ API URL: https://gethmy.com/api
290
+
291
+ Global Context:
292
+ Workspace: <global-ws-id>
293
+ Project: <global-proj-id>
294
+
295
+ Local Context (.harmony-mcp.json):
296
+ Workspace: <local-ws-id>
297
+ Project: <local-proj-id>
298
+
299
+ Active (effective):
300
+ Workspace: <local-ws-id> ← local
301
+ Project: <local-proj-id> ← local
302
+ ```
303
+
304
+ #### Recommended .gitignore
305
+
306
+ Add this to prevent accidentally committing local config:
307
+
308
+ ```
309
+ .harmony-mcp.json
310
+ ```
311
+
247
312
  ## Architecture
248
313
 
249
314
  ```
package/dist/cli.js CHANGED
@@ -25234,12 +25234,16 @@ import { homedir as homedir2 } from "node:os";
25234
25234
  import { join as join2 } from "node:path";
25235
25235
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
25236
25236
  var DEFAULT_API_URL = "https://gethmy.com/api";
25237
+ var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
25237
25238
  function getConfigDir() {
25238
25239
  return join2(homedir2(), ".harmony-mcp");
25239
25240
  }
25240
25241
  function getConfigPath() {
25241
25242
  return join2(getConfigDir(), "config.json");
25242
25243
  }
25244
+ function getLocalConfigPath(cwd) {
25245
+ return join2(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
25246
+ }
25243
25247
  function loadConfig() {
25244
25248
  const configPath = getConfigPath();
25245
25249
  if (!existsSync2(configPath)) {
@@ -25278,6 +25282,39 @@ function saveConfig(config2) {
25278
25282
  const newConfig = { ...existingConfig, ...config2 };
25279
25283
  writeFileSync2(configPath, JSON.stringify(newConfig, null, 2), { mode: 384 });
25280
25284
  }
25285
+ function loadLocalConfig(cwd) {
25286
+ const localConfigPath = getLocalConfigPath(cwd);
25287
+ if (!existsSync2(localConfigPath)) {
25288
+ return null;
25289
+ }
25290
+ try {
25291
+ const data = readFileSync2(localConfigPath, "utf-8");
25292
+ const config2 = JSON.parse(data);
25293
+ return {
25294
+ workspaceId: config2.workspaceId || null,
25295
+ projectId: config2.projectId || null
25296
+ };
25297
+ } catch {
25298
+ return null;
25299
+ }
25300
+ }
25301
+ function saveLocalConfig(config2, cwd) {
25302
+ const localConfigPath = getLocalConfigPath(cwd);
25303
+ const existingConfig = loadLocalConfig(cwd) || {
25304
+ workspaceId: null,
25305
+ projectId: null
25306
+ };
25307
+ const newConfig = { ...existingConfig, ...config2 };
25308
+ const cleanConfig = {};
25309
+ if (newConfig.workspaceId)
25310
+ cleanConfig.workspaceId = newConfig.workspaceId;
25311
+ if (newConfig.projectId)
25312
+ cleanConfig.projectId = newConfig.projectId;
25313
+ writeFileSync2(localConfigPath, JSON.stringify(cleanConfig, null, 2));
25314
+ }
25315
+ function hasLocalConfig(cwd) {
25316
+ return existsSync2(getLocalConfigPath(cwd));
25317
+ }
25281
25318
  function getApiKey() {
25282
25319
  const config2 = loadConfig();
25283
25320
  if (!config2.apiKey) {
@@ -25290,16 +25327,32 @@ function getApiUrl() {
25290
25327
  const config2 = loadConfig();
25291
25328
  return config2.apiUrl;
25292
25329
  }
25293
- function setActiveWorkspace(workspaceId) {
25294
- saveConfig({ activeWorkspaceId: workspaceId });
25330
+ function setActiveWorkspace(workspaceId, options) {
25331
+ if (options?.local) {
25332
+ saveLocalConfig({ workspaceId }, options.cwd);
25333
+ } else {
25334
+ saveConfig({ activeWorkspaceId: workspaceId });
25335
+ }
25295
25336
  }
25296
- function setActiveProject(projectId) {
25297
- saveConfig({ activeProjectId: projectId });
25337
+ function setActiveProject(projectId, options) {
25338
+ if (options?.local) {
25339
+ saveLocalConfig({ projectId }, options.cwd);
25340
+ } else {
25341
+ saveConfig({ activeProjectId: projectId });
25342
+ }
25298
25343
  }
25299
- function getActiveWorkspaceId() {
25344
+ function getActiveWorkspaceId(cwd) {
25345
+ const localConfig = loadLocalConfig(cwd);
25346
+ if (localConfig?.workspaceId) {
25347
+ return localConfig.workspaceId;
25348
+ }
25300
25349
  return loadConfig().activeWorkspaceId;
25301
25350
  }
25302
- function getActiveProjectId() {
25351
+ function getActiveProjectId(cwd) {
25352
+ const localConfig = loadLocalConfig(cwd);
25353
+ if (localConfig?.projectId) {
25354
+ return localConfig.projectId;
25355
+ }
25303
25356
  return loadConfig().activeProjectId;
25304
25357
  }
25305
25358
  function isConfigured() {
@@ -26634,19 +26687,36 @@ You can now use the MCP server with Claude Code.`);
26634
26687
  }
26635
26688
  });
26636
26689
  program.command("status").description("Show configuration status").action(() => {
26637
- const config2 = loadConfig();
26690
+ const globalConfig2 = loadConfig();
26691
+ const localConfig = loadLocalConfig();
26692
+ const hasLocal = hasLocalConfig();
26638
26693
  if (isConfigured()) {
26639
26694
  console.log("Status: Configured");
26640
- console.log(`API Key: ${config2.apiKey?.slice(0, 8)}...`);
26641
- console.log(`API URL: ${config2.apiUrl}`);
26642
- if (config2.activeWorkspaceId) {
26643
- console.log(`Active Workspace: ${config2.activeWorkspaceId}`);
26644
- }
26645
- if (config2.activeProjectId) {
26646
- console.log(`Active Project: ${config2.activeProjectId}`);
26695
+ console.log(`API Key: ${globalConfig2.apiKey?.slice(0, 8)}...`);
26696
+ console.log(`API URL: ${globalConfig2.apiUrl}`);
26697
+ console.log(`
26698
+ Global Context:`);
26699
+ console.log(` Workspace: ${globalConfig2.activeWorkspaceId || "(not set)"}`);
26700
+ console.log(` Project: ${globalConfig2.activeProjectId || "(not set)"}`);
26701
+ if (hasLocal) {
26702
+ console.log(`
26703
+ Local Context (${getLocalConfigPath()}):`);
26704
+ console.log(` Workspace: ${localConfig?.workspaceId || "(not set)"}`);
26705
+ console.log(` Project: ${localConfig?.projectId || "(not set)"}`);
26647
26706
  }
26707
+ const effectiveWorkspace = getActiveWorkspaceId();
26708
+ const effectiveProject = getActiveProjectId();
26648
26709
  console.log(`
26649
- Config file: ${getConfigPath()}`);
26710
+ Active (effective):`);
26711
+ const wsSource = localConfig?.workspaceId ? "local" : globalConfig2.activeWorkspaceId ? "global" : "";
26712
+ const projSource = localConfig?.projectId ? "local" : globalConfig2.activeProjectId ? "global" : "";
26713
+ console.log(` Workspace: ${effectiveWorkspace || "(not set)"}${wsSource ? ` ← ${wsSource}` : ""}`);
26714
+ console.log(` Project: ${effectiveProject || "(not set)"}${projSource ? ` ← ${projSource}` : ""}`);
26715
+ console.log(`
26716
+ Global config: ${getConfigPath()}`);
26717
+ if (hasLocal) {
26718
+ console.log(`Local config: ${getLocalConfigPath()}`);
26719
+ }
26650
26720
  } else {
26651
26721
  console.log("Status: Not configured");
26652
26722
  console.log(`
@@ -26662,15 +26732,27 @@ program.command("reset").description("Remove stored configuration").action(() =>
26662
26732
  });
26663
26733
  console.log("Configuration reset successfully");
26664
26734
  });
26665
- program.command("set-workspace <workspaceId>").description("Set the active workspace context").action((workspaceId) => {
26666
- setActiveWorkspace(workspaceId);
26667
- console.log(`Active workspace set to: ${workspaceId}`);
26735
+ program.command("set-workspace <workspaceId>").description("Set the active workspace context").option("-l, --local", "Save to local project config (.harmony-mcp.json) instead of global").action((workspaceId, options) => {
26736
+ const isLocal = options.local;
26737
+ setActiveWorkspace(workspaceId, { local: isLocal });
26738
+ if (isLocal) {
26739
+ console.log(`Local workspace set to: ${workspaceId}`);
26740
+ console.log(`Config file: ${getLocalConfigPath()}`);
26741
+ } else {
26742
+ console.log(`Global workspace set to: ${workspaceId}`);
26743
+ }
26668
26744
  });
26669
- program.command("set-project <projectId>").description("Set the active project context").action((projectId) => {
26670
- setActiveProject(projectId);
26671
- console.log(`Active project set to: ${projectId}`);
26745
+ program.command("set-project <projectId>").description("Set the active project context").option("-l, --local", "Save to local project config (.harmony-mcp.json) instead of global").action((projectId, options) => {
26746
+ const isLocal = options.local;
26747
+ setActiveProject(projectId, { local: isLocal });
26748
+ if (isLocal) {
26749
+ console.log(`Local project set to: ${projectId}`);
26750
+ console.log(`Config file: ${getLocalConfigPath()}`);
26751
+ } else {
26752
+ console.log(`Global project set to: ${projectId}`);
26753
+ }
26672
26754
  });
26673
- program.command("init").description("Initialize Harmony MCP for AI coding agents (Claude Code, Codex, Cursor, Windsurf)").option("-a, --agent <agents...>", `Agent(s) to configure: ${SUPPORTED_AGENTS.join(", ")}`).option("--all", "Configure all supported agents").option("--detect", "Auto-detect installed agents and configure them").option("-f, --force", "Overwrite existing configuration files").option("-d, --directory <path>", "Project directory (default: current directory)").action(async (options) => {
26755
+ program.command("init").description("Initialize Harmony MCP for AI coding agents (Claude Code, Codex, Cursor, Windsurf)").option("-a, --agent <agents...>", `Agent(s) to configure: ${SUPPORTED_AGENTS.join(", ")}`).option("--all", "Configure all supported agents").option("--detect", "Auto-detect installed agents and configure them").option("-f, --force", "Overwrite existing configuration files").option("-d, --directory <path>", "Project directory (default: current directory)").option("-w, --workspace <id>", "Set local workspace context for this project").option("-p, --project <id>", "Set local project context for this project").action(async (options) => {
26674
26756
  console.log(`Harmony MCP Initialization
26675
26757
  `);
26676
26758
  let agents = [];
@@ -26768,6 +26850,19 @@ ${result.agent.toUpperCase()}:`);
26768
26850
  console.log(" No changes made");
26769
26851
  }
26770
26852
  }
26853
+ if (options.workspace || options.project) {
26854
+ console.log(`
26855
+ LOCAL CONTEXT:`);
26856
+ if (options.workspace) {
26857
+ saveLocalConfig({ workspaceId: options.workspace }, cwd);
26858
+ console.log(` ✓ Workspace: ${options.workspace}`);
26859
+ }
26860
+ if (options.project) {
26861
+ saveLocalConfig({ projectId: options.project }, cwd);
26862
+ console.log(` ✓ Project: ${options.project}`);
26863
+ }
26864
+ console.log(` Config file: ${getLocalConfigPath(cwd)}`);
26865
+ }
26771
26866
  console.log(`
26772
26867
  ` + "─".repeat(60));
26773
26868
  if (!isConfigured()) {
package/dist/index.js CHANGED
@@ -22996,12 +22996,16 @@ import { homedir } from "node:os";
22996
22996
  import { join } from "node:path";
22997
22997
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22998
22998
  var DEFAULT_API_URL = "https://gethmy.com/api";
22999
+ var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
22999
23000
  function getConfigDir() {
23000
23001
  return join(homedir(), ".harmony-mcp");
23001
23002
  }
23002
23003
  function getConfigPath() {
23003
23004
  return join(getConfigDir(), "config.json");
23004
23005
  }
23006
+ function getLocalConfigPath(cwd) {
23007
+ return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
23008
+ }
23005
23009
  function loadConfig() {
23006
23010
  const configPath = getConfigPath();
23007
23011
  if (!existsSync(configPath)) {
@@ -23040,6 +23044,39 @@ function saveConfig(config2) {
23040
23044
  const newConfig = { ...existingConfig, ...config2 };
23041
23045
  writeFileSync(configPath, JSON.stringify(newConfig, null, 2), { mode: 384 });
23042
23046
  }
23047
+ function loadLocalConfig(cwd) {
23048
+ const localConfigPath = getLocalConfigPath(cwd);
23049
+ if (!existsSync(localConfigPath)) {
23050
+ return null;
23051
+ }
23052
+ try {
23053
+ const data = readFileSync(localConfigPath, "utf-8");
23054
+ const config2 = JSON.parse(data);
23055
+ return {
23056
+ workspaceId: config2.workspaceId || null,
23057
+ projectId: config2.projectId || null
23058
+ };
23059
+ } catch {
23060
+ return null;
23061
+ }
23062
+ }
23063
+ function saveLocalConfig(config2, cwd) {
23064
+ const localConfigPath = getLocalConfigPath(cwd);
23065
+ const existingConfig = loadLocalConfig(cwd) || {
23066
+ workspaceId: null,
23067
+ projectId: null
23068
+ };
23069
+ const newConfig = { ...existingConfig, ...config2 };
23070
+ const cleanConfig = {};
23071
+ if (newConfig.workspaceId)
23072
+ cleanConfig.workspaceId = newConfig.workspaceId;
23073
+ if (newConfig.projectId)
23074
+ cleanConfig.projectId = newConfig.projectId;
23075
+ writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
23076
+ }
23077
+ function hasLocalConfig(cwd) {
23078
+ return existsSync(getLocalConfigPath(cwd));
23079
+ }
23043
23080
  function getApiKey() {
23044
23081
  const config2 = loadConfig();
23045
23082
  if (!config2.apiKey) {
@@ -23052,16 +23089,32 @@ function getApiUrl() {
23052
23089
  const config2 = loadConfig();
23053
23090
  return config2.apiUrl;
23054
23091
  }
23055
- function setActiveWorkspace(workspaceId) {
23056
- saveConfig({ activeWorkspaceId: workspaceId });
23092
+ function setActiveWorkspace(workspaceId, options) {
23093
+ if (options?.local) {
23094
+ saveLocalConfig({ workspaceId }, options.cwd);
23095
+ } else {
23096
+ saveConfig({ activeWorkspaceId: workspaceId });
23097
+ }
23057
23098
  }
23058
- function setActiveProject(projectId) {
23059
- saveConfig({ activeProjectId: projectId });
23099
+ function setActiveProject(projectId, options) {
23100
+ if (options?.local) {
23101
+ saveLocalConfig({ projectId }, options.cwd);
23102
+ } else {
23103
+ saveConfig({ activeProjectId: projectId });
23104
+ }
23060
23105
  }
23061
- function getActiveWorkspaceId() {
23106
+ function getActiveWorkspaceId(cwd) {
23107
+ const localConfig = loadLocalConfig(cwd);
23108
+ if (localConfig?.workspaceId) {
23109
+ return localConfig.workspaceId;
23110
+ }
23062
23111
  return loadConfig().activeWorkspaceId;
23063
23112
  }
23064
- function getActiveProjectId() {
23113
+ function getActiveProjectId(cwd) {
23114
+ const localConfig = loadLocalConfig(cwd);
23115
+ if (localConfig?.projectId) {
23116
+ return localConfig.projectId;
23117
+ }
23065
23118
  return loadConfig().activeProjectId;
23066
23119
  }
23067
23120
  function isConfigured() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harmony-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",