@codazen/harmonica-mcp 3.1.0 → 3.2.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/dist/index.js +181 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22444,7 +22444,8 @@ var CHECK_MAX_SCORES = {
|
|
|
22444
22444
|
pii_scan: 100,
|
|
22445
22445
|
portfolio_coherence: 5,
|
|
22446
22446
|
revision_quality: 5,
|
|
22447
|
-
drop_quality: 5
|
|
22447
|
+
drop_quality: 5,
|
|
22448
|
+
root_coda_quality: 5
|
|
22448
22449
|
};
|
|
22449
22450
|
|
|
22450
22451
|
// ../../libs/harmonica-services/src/mcp/tools/beat-quality-tools.ts
|
|
@@ -22648,6 +22649,17 @@ var NOTE_TYPE_LABELS = {
|
|
|
22648
22649
|
decision: "Decisions",
|
|
22649
22650
|
document: "Documents"
|
|
22650
22651
|
};
|
|
22652
|
+
function formatDeliveryPolicy(policy, unresolvedReason) {
|
|
22653
|
+
const LABELS = {
|
|
22654
|
+
openPullRequest: "Open a pull request",
|
|
22655
|
+
requestAgentReview: "Request the agent code review",
|
|
22656
|
+
watchPullRequest: "Watch the pull request to green"
|
|
22657
|
+
};
|
|
22658
|
+
const verdict = (value) => value === true ? "yes \u2014 proceed without asking" : value === false ? "NO \u2014 prohibited, say so rather than asking" : "not set \u2014 ask first";
|
|
22659
|
+
const lines = Object.keys(LABELS).map((step) => `- ${LABELS[step]}: ${verdict(policy?.[step] ?? void 0)}`);
|
|
22660
|
+
const footer = unresolvedReason ? [`- _Policy could not be resolved (${unresolvedReason}) \u2014 ask first; this is not proof that no policy is set._`] : [];
|
|
22661
|
+
return ["## Agent Delivery Policy", ...lines, ...footer].join("\n");
|
|
22662
|
+
}
|
|
22651
22663
|
function formatProjectContext(project, notes, orgCoda) {
|
|
22652
22664
|
const sections = [];
|
|
22653
22665
|
sections.push(`# Project: ${project.title}`);
|
|
@@ -22657,6 +22669,8 @@ function formatProjectContext(project, notes, orgCoda) {
|
|
|
22657
22669
|
const branch = project.repoDefaultBranch ?? "main";
|
|
22658
22670
|
sections.push(`**Repo:** ${project.repoOwner}/${project.repoName} (branch: ${branch})`);
|
|
22659
22671
|
}
|
|
22672
|
+
sections.push(`
|
|
22673
|
+
${formatDeliveryPolicy(project.agentDeliveryPolicy)}`);
|
|
22660
22674
|
if (orgCoda) {
|
|
22661
22675
|
sections.push(`
|
|
22662
22676
|
## Organization Coda
|
|
@@ -23255,7 +23269,14 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
|
|
|
23255
23269
|
return { content: [{ type: "text", text: `Revision not found: ${revisionId}` }], isError: true };
|
|
23256
23270
|
}
|
|
23257
23271
|
await assertBeatInOrg(client, revision.beatId, ctx.orgId);
|
|
23258
|
-
|
|
23272
|
+
const resolution = await client.resolveDeliveryPolicy(revisionId);
|
|
23273
|
+
const policyBlock = `
|
|
23274
|
+
|
|
23275
|
+
${formatDeliveryPolicy(
|
|
23276
|
+
resolution.resolved ? resolution.policy : void 0,
|
|
23277
|
+
resolution.resolved ? void 0 : resolution.reason
|
|
23278
|
+
)}`;
|
|
23279
|
+
return { content: [{ type: "text", text: formatRevisionDetail(revision) + policyBlock }] };
|
|
23259
23280
|
} catch (err) {
|
|
23260
23281
|
const message = err instanceof Error ? err.message : String(err);
|
|
23261
23282
|
return { content: [{ type: "text", text: `Failed to get revision: ${message}` }], isError: true };
|
|
@@ -26200,6 +26221,7 @@ var ScopeNotebookApiSchema = external_exports.object({
|
|
|
26200
26221
|
var NotebookResponseSchema = external_exports.object({ notebook: NotebookApiSchema }).passthrough();
|
|
26201
26222
|
var NotebookScopesResponseSchema = external_exports.object({ scopes: external_exports.array(NotebookScopeRefSchema) }).passthrough();
|
|
26202
26223
|
var ScopeNotebooksResponseSchema = external_exports.object({ notebooks: external_exports.array(ScopeNotebookApiSchema) }).passthrough();
|
|
26224
|
+
var NotebookSharingResponseSchema = external_exports.object({ guestView: external_exports.boolean() }).passthrough();
|
|
26203
26225
|
var NotebookShortcutApiSchema = external_exports.object({
|
|
26204
26226
|
notebookId: external_exports.string(),
|
|
26205
26227
|
scopeType: NotebookScopeTypeSchema,
|
|
@@ -26325,7 +26347,8 @@ var CHECK_TARGET_TYPES = [
|
|
|
26325
26347
|
"revision",
|
|
26326
26348
|
"beat_version",
|
|
26327
26349
|
"project",
|
|
26328
|
-
"drop"
|
|
26350
|
+
"drop",
|
|
26351
|
+
"organization"
|
|
26329
26352
|
];
|
|
26330
26353
|
var CheckTargetTypeSchema = external_exports.enum(CHECK_TARGET_TYPES);
|
|
26331
26354
|
var CheckApiSchema = external_exports.object({
|
|
@@ -26577,7 +26600,7 @@ var StoredMessageSchema = external_exports.object({
|
|
|
26577
26600
|
var ConversationsResponseSchema = external_exports.array(ConversationSummarySchema);
|
|
26578
26601
|
var MessagesResponseSchema = external_exports.array(StoredMessageSchema);
|
|
26579
26602
|
var StartSessionRequestBodySchema = external_exports.object({
|
|
26580
|
-
|
|
26603
|
+
systemId: external_exports.string().min(1),
|
|
26581
26604
|
message: external_exports.string().min(1),
|
|
26582
26605
|
revisionId: external_exports.string().min(1).optional(),
|
|
26583
26606
|
enableWebSearch: external_exports.boolean().optional()
|
|
@@ -28487,6 +28510,59 @@ Notes filed: ${createdNotes.length}`);
|
|
|
28487
28510
|
}
|
|
28488
28511
|
|
|
28489
28512
|
// ../../libs/harmonica-services/src/mcp/tools/organization-tools.ts
|
|
28513
|
+
var ROOT_CODA_POLL_INTERVAL_MS = 4e3;
|
|
28514
|
+
var ROOT_CODA_POLL_TIMEOUT_MS = 18e4;
|
|
28515
|
+
var ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
28516
|
+
async function pollForRootCodaCheck(client, taskId) {
|
|
28517
|
+
const deadline = Date.now() + ROOT_CODA_POLL_TIMEOUT_MS;
|
|
28518
|
+
let consecutiveErrors = 0;
|
|
28519
|
+
while (Date.now() < deadline) {
|
|
28520
|
+
const [task, err] = await client.getTask(taskId).then(
|
|
28521
|
+
(t) => [t, null],
|
|
28522
|
+
(e) => [null, e]
|
|
28523
|
+
);
|
|
28524
|
+
if (err) {
|
|
28525
|
+
consecutiveErrors++;
|
|
28526
|
+
if (consecutiveErrors >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS) {
|
|
28527
|
+
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
28528
|
+
}
|
|
28529
|
+
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
28530
|
+
continue;
|
|
28531
|
+
}
|
|
28532
|
+
consecutiveErrors = 0;
|
|
28533
|
+
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
28534
|
+
if (task.status === "completed") {
|
|
28535
|
+
if (!task.result) throw new Error(`Task ${taskId} completed with no result`);
|
|
28536
|
+
const result = task.result;
|
|
28537
|
+
if (!Array.isArray(result.dimensions) || typeof result.overallScore !== "number") {
|
|
28538
|
+
throw new Error(`Task ${taskId} did not return a root Coda scorecard \u2014 was it a run_check task?`);
|
|
28539
|
+
}
|
|
28540
|
+
return result;
|
|
28541
|
+
}
|
|
28542
|
+
if (task.status === "failed") throw new Error(`Root Coda quality check failed: ${task.error ?? "unknown error"}`);
|
|
28543
|
+
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
28544
|
+
}
|
|
28545
|
+
throw new Error(`Root Coda quality check timed out after ${ROOT_CODA_POLL_TIMEOUT_MS / 1e3}s`);
|
|
28546
|
+
}
|
|
28547
|
+
function formatRootCodaReport(check2) {
|
|
28548
|
+
const bar = (score, max) => {
|
|
28549
|
+
const filled = Math.max(0, Math.min(max, Math.round(score)));
|
|
28550
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(max - filled);
|
|
28551
|
+
};
|
|
28552
|
+
const dims = check2.dimensions.map(
|
|
28553
|
+
(d) => `**${d.label}** ${bar(d.score, d.maxScore)} ${d.score}/${d.maxScore}
|
|
28554
|
+
${d.rationale}${d.suggestions?.length ? `
|
|
28555
|
+
\u2022 ${d.suggestions.join("\n\u2022 ")}` : ""}`
|
|
28556
|
+
).join("\n\n");
|
|
28557
|
+
return [
|
|
28558
|
+
`## Root Coda Quality \u2014 ${check2.targetId}`,
|
|
28559
|
+
`**Overall Score:** ${check2.overallScore}/5`,
|
|
28560
|
+
`**Summary:** ${check2.summary}`,
|
|
28561
|
+
check2.topSuggestion ? `**Top Suggestion:** ${check2.topSuggestion}` : "",
|
|
28562
|
+
"",
|
|
28563
|
+
dims
|
|
28564
|
+
].filter(Boolean).join("\n");
|
|
28565
|
+
}
|
|
28490
28566
|
function registerOrganizationTools(server, ctx, client) {
|
|
28491
28567
|
server.tool(
|
|
28492
28568
|
"get_organization",
|
|
@@ -28616,6 +28692,67 @@ function registerOrganizationTools(server, ctx, client) {
|
|
|
28616
28692
|
return { content: [{ type: "text", text }] };
|
|
28617
28693
|
}
|
|
28618
28694
|
);
|
|
28695
|
+
server.tool(
|
|
28696
|
+
"compose_root_coda",
|
|
28697
|
+
"Generate and apply an Organization's root Coda (top-level purpose statement) from its name, industry, description, and existing Coda if any. Drafts via LLM, applies directly to the Organization, then runs a root_coda_quality check \u2014 if the first score is below 4.0, performs exactly one automatic self-revision pass incorporating the feedback before finishing. Returns a job ID \u2014 use get_job_status to track progress.",
|
|
28698
|
+
{
|
|
28699
|
+
orgId: external_exports.string().min(1).describe("The organization ID")
|
|
28700
|
+
},
|
|
28701
|
+
async ({ orgId }) => {
|
|
28702
|
+
try {
|
|
28703
|
+
const task = await client.composeRootCoda(orgId);
|
|
28704
|
+
return {
|
|
28705
|
+
content: [{
|
|
28706
|
+
type: "text",
|
|
28707
|
+
text: [
|
|
28708
|
+
"Root Coda composition queued.",
|
|
28709
|
+
"",
|
|
28710
|
+
`**Organization:** ${orgId}`,
|
|
28711
|
+
`**Task ID:** ${task.taskId}`,
|
|
28712
|
+
`**Status:** ${task.status}`,
|
|
28713
|
+
"",
|
|
28714
|
+
"The agent will gather context, draft a Coda, apply it to the Organization, and grade it \u2014 revising once automatically if the first score is below 4.0.",
|
|
28715
|
+
"Use `get_job_status` with the job ID to check progress."
|
|
28716
|
+
].join("\n")
|
|
28717
|
+
}]
|
|
28718
|
+
};
|
|
28719
|
+
} catch (err) {
|
|
28720
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28721
|
+
return { content: [{ type: "text", text: `compose_root_coda failed: ${message}` }], isError: true };
|
|
28722
|
+
}
|
|
28723
|
+
}
|
|
28724
|
+
);
|
|
28725
|
+
server.tool(
|
|
28726
|
+
"check_root_coda_quality",
|
|
28727
|
+
"Run a six-dimension quality check on an Organization's root Coda (Discriminating, Attributed, Reciprocal, Bounded, Generative, Consequential). By default returns a taskId immediately \u2014 set wait=true to block and receive the full scorecard inline.",
|
|
28728
|
+
{
|
|
28729
|
+
orgId: external_exports.string().min(1).describe("The organization ID"),
|
|
28730
|
+
wait: external_exports.boolean().optional().describe("Block until the check completes and return the scorecard inline. Default: false.")
|
|
28731
|
+
},
|
|
28732
|
+
async ({ orgId, wait }) => {
|
|
28733
|
+
try {
|
|
28734
|
+
const { taskId } = await client.runCheck(orgId, "root_coda_quality", orgId);
|
|
28735
|
+
if (!wait) {
|
|
28736
|
+
return {
|
|
28737
|
+
content: [{
|
|
28738
|
+
type: "text",
|
|
28739
|
+
text: [
|
|
28740
|
+
`Root Coda quality check enqueued for ${orgId}.`,
|
|
28741
|
+
`Task ID: ${taskId}`,
|
|
28742
|
+
"",
|
|
28743
|
+
`Use get_job_status with jobId="${taskId}" to poll, or list_checks with checkType="root_coda_quality" to view results.`
|
|
28744
|
+
].join("\n")
|
|
28745
|
+
}]
|
|
28746
|
+
};
|
|
28747
|
+
}
|
|
28748
|
+
const check2 = await pollForRootCodaCheck(client, taskId);
|
|
28749
|
+
return { content: [{ type: "text", text: formatRootCodaReport(check2) }] };
|
|
28750
|
+
} catch (err) {
|
|
28751
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28752
|
+
return { content: [{ type: "text", text: `Root Coda quality check failed: ${message}` }], isError: true };
|
|
28753
|
+
}
|
|
28754
|
+
}
|
|
28755
|
+
);
|
|
28619
28756
|
}
|
|
28620
28757
|
|
|
28621
28758
|
// ../../libs/harmonica-services/src/mcp/tools/plan-quality-tools.ts
|
|
@@ -30316,7 +30453,12 @@ function registerProjectTools(server, ctx, client) {
|
|
|
30316
30453
|
repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
|
|
30317
30454
|
repoName: external_exports.string().optional().describe("GitHub repository name"),
|
|
30318
30455
|
repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")'),
|
|
30319
|
-
rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.')
|
|
30456
|
+
rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.'),
|
|
30457
|
+
agentDeliveryPolicy: external_exports.object({
|
|
30458
|
+
openPullRequest: external_exports.boolean().nullable().optional().describe("Open a pull request when the work is ready."),
|
|
30459
|
+
requestAgentReview: external_exports.boolean().nullable().optional().describe("Request the agent code review (the agent-review label)."),
|
|
30460
|
+
watchPullRequest: external_exports.boolean().nullable().optional().describe("Watch the pull request and drive it to green.")
|
|
30461
|
+
}).optional().describe("Delivery latitude for agents on this System: which pull-request steps they may take without asking. Each field is tri-state \u2014 omit it to leave it alone, true to let agents proceed, false to forbid the step outright, null to clear it back to asking.")
|
|
30320
30462
|
};
|
|
30321
30463
|
const updateSystemHandler = async ({ systemId, ...updates }) => {
|
|
30322
30464
|
try {
|
|
@@ -31867,7 +32009,7 @@ function createHttpClient(config2) {
|
|
|
31867
32009
|
const result = await request(
|
|
31868
32010
|
"POST",
|
|
31869
32011
|
`/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-project`,
|
|
31870
|
-
{ projectId, targetOrgId }
|
|
32012
|
+
{ systemId: projectId, targetOrgId }
|
|
31871
32013
|
);
|
|
31872
32014
|
const project = result?.project ? {
|
|
31873
32015
|
...result.project,
|
|
@@ -32975,6 +33117,15 @@ function createHttpClient(config2) {
|
|
|
32975
33117
|
if (!result?.taskId) throw new Error("Draft coda enqueue failed: no taskId returned");
|
|
32976
33118
|
return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
32977
33119
|
},
|
|
33120
|
+
composeRootCoda: async (orgId) => {
|
|
33121
|
+
const result = await request(
|
|
33122
|
+
"POST",
|
|
33123
|
+
`/api/organizations/${encodeURIComponent(orgId)}/compose-root-coda`,
|
|
33124
|
+
{}
|
|
33125
|
+
);
|
|
33126
|
+
if (!result?.taskId) throw new Error("Compose root Coda enqueue failed: no taskId returned");
|
|
33127
|
+
return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending", projectId: orgId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
33128
|
+
},
|
|
32978
33129
|
validateAssumptions: async (projectId, noteIds) => {
|
|
32979
33130
|
const result = await request(
|
|
32980
33131
|
"POST",
|
|
@@ -33042,7 +33193,17 @@ function createHttpClient(config2) {
|
|
|
33042
33193
|
return download;
|
|
33043
33194
|
},
|
|
33044
33195
|
importSystemSnapshot: (snapshot, options) => request("POST", "/api/systems/import", { ...snapshot, importOptions: options }, LONG_RUNNING_TIMEOUT_MS),
|
|
33045
|
-
importSystemSnapshotFromUrl: (url2, options) =>
|
|
33196
|
+
importSystemSnapshotFromUrl: async (url2, options) => {
|
|
33197
|
+
const enqueued = await request(
|
|
33198
|
+
"POST",
|
|
33199
|
+
"/api/systems/import",
|
|
33200
|
+
{ snapshotUrl: url2, importOptions: options }
|
|
33201
|
+
);
|
|
33202
|
+
if (!enqueued?.taskId) {
|
|
33203
|
+
throw new Error("importSystemSnapshotFromUrl: API did not return a taskId");
|
|
33204
|
+
}
|
|
33205
|
+
return pollTaskResult(enqueued.taskId);
|
|
33206
|
+
},
|
|
33046
33207
|
// Embedding similarity
|
|
33047
33208
|
embedProjectEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
|
|
33048
33209
|
findSimilarNotes: async (noteId, projectId, options) => {
|
|
@@ -33438,6 +33599,18 @@ function createHttpClient(config2) {
|
|
|
33438
33599
|
// Systems owned directly by an Account (B-278 v17). A 404 here means either
|
|
33439
33600
|
// no such Account or the feature flag is off; both read as "nothing to show",
|
|
33440
33601
|
// which is why request() returning undefined maps to an empty list.
|
|
33602
|
+
// Delegates to the endpoint that calls the same service the direct adapter
|
|
33603
|
+
// uses, so neither adapter carries its own copy of the fail-closed rule.
|
|
33604
|
+
// request() maps a 404 to undefined; that is itself unresolvable, so it
|
|
33605
|
+
// becomes an explicit unresolved result rather than a thrown error.
|
|
33606
|
+
resolveDeliveryPolicy: async (revisionId) => {
|
|
33607
|
+
const res = await request(
|
|
33608
|
+
"GET",
|
|
33609
|
+
`/api/revisions/${encodeURIComponent(revisionId)}/delivery-policy`
|
|
33610
|
+
);
|
|
33611
|
+
if (!res) return { resolved: false, reason: "revision_not_found" };
|
|
33612
|
+
return res;
|
|
33613
|
+
},
|
|
33441
33614
|
listAccountSystems: async (accountId) => {
|
|
33442
33615
|
const res = await request("GET", `/api/accounts/${encodeURIComponent(accountId)}/systems`);
|
|
33443
33616
|
return res?.systems ?? [];
|
|
@@ -33873,7 +34046,7 @@ function loadConfig() {
|
|
|
33873
34046
|
};
|
|
33874
34047
|
}
|
|
33875
34048
|
async function main() {
|
|
33876
|
-
console.error(`[harmonica-mcp] v${"3.
|
|
34049
|
+
console.error(`[harmonica-mcp] v${"3.2.0"} starting\u2026`);
|
|
33877
34050
|
const config2 = loadConfig();
|
|
33878
34051
|
const client = createHttpClient({
|
|
33879
34052
|
apiBaseUrl: config2.apiBaseUrl,
|