harmony-mcp 1.6.2 → 1.6.3
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/dist/cli.js +86 -13
- package/dist/index.js +86 -13
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -29506,6 +29506,50 @@ function hasProjectContext(cwd) {
|
|
|
29506
29506
|
}
|
|
29507
29507
|
|
|
29508
29508
|
// src/api-client.ts
|
|
29509
|
+
var RETRY_CONFIG = {
|
|
29510
|
+
maxRetries: 3,
|
|
29511
|
+
baseDelayMs: 1000,
|
|
29512
|
+
maxDelayMs: 1e4,
|
|
29513
|
+
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
|
|
29514
|
+
};
|
|
29515
|
+
function isRetryableError(error49, status) {
|
|
29516
|
+
if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
|
|
29517
|
+
return true;
|
|
29518
|
+
}
|
|
29519
|
+
if (error49 instanceof TypeError)
|
|
29520
|
+
return true;
|
|
29521
|
+
const msg = error49 instanceof Error ? error49.message : String(error49);
|
|
29522
|
+
return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed");
|
|
29523
|
+
}
|
|
29524
|
+
function getRetryDelay(attempt) {
|
|
29525
|
+
const delay = Math.min(RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt), RETRY_CONFIG.maxDelayMs);
|
|
29526
|
+
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
29527
|
+
}
|
|
29528
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
29529
|
+
|
|
29530
|
+
class Semaphore {
|
|
29531
|
+
permits;
|
|
29532
|
+
queue = [];
|
|
29533
|
+
constructor(permits) {
|
|
29534
|
+
this.permits = permits;
|
|
29535
|
+
}
|
|
29536
|
+
async acquire() {
|
|
29537
|
+
if (this.permits > 0) {
|
|
29538
|
+
this.permits--;
|
|
29539
|
+
return;
|
|
29540
|
+
}
|
|
29541
|
+
return new Promise((resolve) => this.queue.push(resolve));
|
|
29542
|
+
}
|
|
29543
|
+
release() {
|
|
29544
|
+
const next = this.queue.shift();
|
|
29545
|
+
if (next)
|
|
29546
|
+
next();
|
|
29547
|
+
else
|
|
29548
|
+
this.permits++;
|
|
29549
|
+
}
|
|
29550
|
+
}
|
|
29551
|
+
var requestSemaphore = new Semaphore(3);
|
|
29552
|
+
|
|
29509
29553
|
class HarmonyApiClient {
|
|
29510
29554
|
apiKey;
|
|
29511
29555
|
apiUrl;
|
|
@@ -29514,20 +29558,49 @@ class HarmonyApiClient {
|
|
|
29514
29558
|
this.apiUrl = getApiUrl();
|
|
29515
29559
|
}
|
|
29516
29560
|
async request(method, path, body) {
|
|
29561
|
+
await requestSemaphore.acquire();
|
|
29562
|
+
try {
|
|
29563
|
+
return await this.requestWithRetry(method, path, body);
|
|
29564
|
+
} finally {
|
|
29565
|
+
requestSemaphore.release();
|
|
29566
|
+
}
|
|
29567
|
+
}
|
|
29568
|
+
async requestWithRetry(method, path, body) {
|
|
29517
29569
|
const url2 = `${this.apiUrl}/v1${path}`;
|
|
29518
|
-
|
|
29519
|
-
|
|
29520
|
-
|
|
29521
|
-
|
|
29522
|
-
|
|
29523
|
-
|
|
29524
|
-
|
|
29525
|
-
|
|
29526
|
-
|
|
29527
|
-
|
|
29528
|
-
|
|
29570
|
+
let lastError = null;
|
|
29571
|
+
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
29572
|
+
try {
|
|
29573
|
+
const response = await fetch(url2, {
|
|
29574
|
+
method,
|
|
29575
|
+
headers: {
|
|
29576
|
+
"Content-Type": "application/json",
|
|
29577
|
+
"X-API-Key": this.apiKey
|
|
29578
|
+
},
|
|
29579
|
+
body: body ? JSON.stringify(body) : undefined
|
|
29580
|
+
});
|
|
29581
|
+
const data = await response.json();
|
|
29582
|
+
if (!response.ok) {
|
|
29583
|
+
if (!isRetryableError(null, response.status)) {
|
|
29584
|
+
throw new Error(data.error || `API error: ${response.status}`);
|
|
29585
|
+
}
|
|
29586
|
+
lastError = new Error(data.error || `API error: ${response.status}`);
|
|
29587
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
29588
|
+
await sleep(getRetryDelay(attempt));
|
|
29589
|
+
continue;
|
|
29590
|
+
}
|
|
29591
|
+
}
|
|
29592
|
+
return data;
|
|
29593
|
+
} catch (error49) {
|
|
29594
|
+
lastError = error49 instanceof Error ? error49 : new Error(String(error49));
|
|
29595
|
+
if (!isRetryableError(error49))
|
|
29596
|
+
throw lastError;
|
|
29597
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
29598
|
+
await sleep(getRetryDelay(attempt));
|
|
29599
|
+
continue;
|
|
29600
|
+
}
|
|
29601
|
+
}
|
|
29529
29602
|
}
|
|
29530
|
-
|
|
29603
|
+
throw lastError || new Error("Request failed after retries");
|
|
29531
29604
|
}
|
|
29532
29605
|
async listWorkspaces() {
|
|
29533
29606
|
return this.request("GET", "/workspaces");
|
|
@@ -30277,7 +30350,7 @@ var TOOLS = {
|
|
|
30277
30350
|
cardId: { type: "string", description: "Card ID with active session" },
|
|
30278
30351
|
agentIdentifier: { type: "string", description: "Agent identifier" },
|
|
30279
30352
|
agentName: { type: "string", description: "Agent name" },
|
|
30280
|
-
status: { type: "string", enum: ["working", "blocked", "paused"], description: "Current status" },
|
|
30353
|
+
status: { type: "string", enum: ["working", "blocked", "waiting", "paused"], description: "Current status" },
|
|
30281
30354
|
progressPercent: { type: "number", description: "Progress percentage (0-100)" },
|
|
30282
30355
|
currentTask: { type: "string", description: "What the agent is currently doing" },
|
|
30283
30356
|
blockers: { type: "array", items: { type: "string" }, description: "List of blocking issues" },
|
package/dist/index.js
CHANGED
|
@@ -27269,6 +27269,50 @@ function hasProjectContext(cwd) {
|
|
|
27269
27269
|
}
|
|
27270
27270
|
|
|
27271
27271
|
// src/api-client.ts
|
|
27272
|
+
var RETRY_CONFIG = {
|
|
27273
|
+
maxRetries: 3,
|
|
27274
|
+
baseDelayMs: 1000,
|
|
27275
|
+
maxDelayMs: 1e4,
|
|
27276
|
+
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
|
|
27277
|
+
};
|
|
27278
|
+
function isRetryableError(error49, status) {
|
|
27279
|
+
if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
|
|
27280
|
+
return true;
|
|
27281
|
+
}
|
|
27282
|
+
if (error49 instanceof TypeError)
|
|
27283
|
+
return true;
|
|
27284
|
+
const msg = error49 instanceof Error ? error49.message : String(error49);
|
|
27285
|
+
return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed");
|
|
27286
|
+
}
|
|
27287
|
+
function getRetryDelay(attempt) {
|
|
27288
|
+
const delay = Math.min(RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt), RETRY_CONFIG.maxDelayMs);
|
|
27289
|
+
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
27290
|
+
}
|
|
27291
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
27292
|
+
|
|
27293
|
+
class Semaphore {
|
|
27294
|
+
permits;
|
|
27295
|
+
queue = [];
|
|
27296
|
+
constructor(permits) {
|
|
27297
|
+
this.permits = permits;
|
|
27298
|
+
}
|
|
27299
|
+
async acquire() {
|
|
27300
|
+
if (this.permits > 0) {
|
|
27301
|
+
this.permits--;
|
|
27302
|
+
return;
|
|
27303
|
+
}
|
|
27304
|
+
return new Promise((resolve) => this.queue.push(resolve));
|
|
27305
|
+
}
|
|
27306
|
+
release() {
|
|
27307
|
+
const next = this.queue.shift();
|
|
27308
|
+
if (next)
|
|
27309
|
+
next();
|
|
27310
|
+
else
|
|
27311
|
+
this.permits++;
|
|
27312
|
+
}
|
|
27313
|
+
}
|
|
27314
|
+
var requestSemaphore = new Semaphore(3);
|
|
27315
|
+
|
|
27272
27316
|
class HarmonyApiClient {
|
|
27273
27317
|
apiKey;
|
|
27274
27318
|
apiUrl;
|
|
@@ -27277,20 +27321,49 @@ class HarmonyApiClient {
|
|
|
27277
27321
|
this.apiUrl = getApiUrl();
|
|
27278
27322
|
}
|
|
27279
27323
|
async request(method, path, body) {
|
|
27324
|
+
await requestSemaphore.acquire();
|
|
27325
|
+
try {
|
|
27326
|
+
return await this.requestWithRetry(method, path, body);
|
|
27327
|
+
} finally {
|
|
27328
|
+
requestSemaphore.release();
|
|
27329
|
+
}
|
|
27330
|
+
}
|
|
27331
|
+
async requestWithRetry(method, path, body) {
|
|
27280
27332
|
const url2 = `${this.apiUrl}/v1${path}`;
|
|
27281
|
-
|
|
27282
|
-
|
|
27283
|
-
|
|
27284
|
-
|
|
27285
|
-
|
|
27286
|
-
|
|
27287
|
-
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
|
|
27291
|
-
|
|
27333
|
+
let lastError = null;
|
|
27334
|
+
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
27335
|
+
try {
|
|
27336
|
+
const response = await fetch(url2, {
|
|
27337
|
+
method,
|
|
27338
|
+
headers: {
|
|
27339
|
+
"Content-Type": "application/json",
|
|
27340
|
+
"X-API-Key": this.apiKey
|
|
27341
|
+
},
|
|
27342
|
+
body: body ? JSON.stringify(body) : undefined
|
|
27343
|
+
});
|
|
27344
|
+
const data = await response.json();
|
|
27345
|
+
if (!response.ok) {
|
|
27346
|
+
if (!isRetryableError(null, response.status)) {
|
|
27347
|
+
throw new Error(data.error || `API error: ${response.status}`);
|
|
27348
|
+
}
|
|
27349
|
+
lastError = new Error(data.error || `API error: ${response.status}`);
|
|
27350
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
27351
|
+
await sleep(getRetryDelay(attempt));
|
|
27352
|
+
continue;
|
|
27353
|
+
}
|
|
27354
|
+
}
|
|
27355
|
+
return data;
|
|
27356
|
+
} catch (error49) {
|
|
27357
|
+
lastError = error49 instanceof Error ? error49 : new Error(String(error49));
|
|
27358
|
+
if (!isRetryableError(error49))
|
|
27359
|
+
throw lastError;
|
|
27360
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
27361
|
+
await sleep(getRetryDelay(attempt));
|
|
27362
|
+
continue;
|
|
27363
|
+
}
|
|
27364
|
+
}
|
|
27292
27365
|
}
|
|
27293
|
-
|
|
27366
|
+
throw lastError || new Error("Request failed after retries");
|
|
27294
27367
|
}
|
|
27295
27368
|
async listWorkspaces() {
|
|
27296
27369
|
return this.request("GET", "/workspaces");
|
|
@@ -28040,7 +28113,7 @@ var TOOLS = {
|
|
|
28040
28113
|
cardId: { type: "string", description: "Card ID with active session" },
|
|
28041
28114
|
agentIdentifier: { type: "string", description: "Agent identifier" },
|
|
28042
28115
|
agentName: { type: "string", description: "Agent name" },
|
|
28043
|
-
status: { type: "string", enum: ["working", "blocked", "paused"], description: "Current status" },
|
|
28116
|
+
status: { type: "string", enum: ["working", "blocked", "waiting", "paused"], description: "Current status" },
|
|
28044
28117
|
progressPercent: { type: "number", description: "Progress percentage (0-100)" },
|
|
28045
28118
|
currentTask: { type: "string", description: "What the agent is currently doing" },
|
|
28046
28119
|
blockers: { type: "array", items: { type: "string" }, description: "List of blocking issues" },
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harmony-mcp",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
5
8
|
"type": "module",
|
|
6
9
|
"main": "dist/index.js",
|
|
7
10
|
"bin": {
|