@codazen/harmonica-mcp 4.0.1 → 4.0.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.
Files changed (2) hide show
  1. package/dist/index.js +60 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25533,17 +25533,14 @@ function registerMembershipTools(server, ctx, client) {
25533
25533
  );
25534
25534
  server.tool(
25535
25535
  "add_member",
25536
- "Add a user to the configured organization. Creates the user record if they do not exist yet.",
25536
+ "Grant a user access to the configured organization. Their name and avatar are filled in when they first sign in.",
25537
25537
  {
25538
- name: external_exports.string().describe("Full name of the user"),
25539
25538
  email: external_exports.string().email().describe("Email address (used as the user ID)"),
25540
- title: external_exports.string().optional().describe("Job title"),
25541
- responsibility: external_exports.string().optional().describe("Primary responsibility or role description"),
25542
25539
  role: external_exports.enum(["member", "admin", "client"]).optional().describe('Organization role (defaults to "member")')
25543
25540
  },
25544
- async ({ name, email: email2, title, responsibility, role }) => {
25541
+ async ({ email: email2, role }) => {
25545
25542
  try {
25546
- const member = await client.addOrgMember(ctx.orgId, { name, email: email2, title, responsibility, role });
25543
+ const member = await client.addOrgMember(ctx.orgId, { email: email2, role });
25547
25544
  const lines = [
25548
25545
  "Member added successfully.",
25549
25546
  "",
@@ -25562,17 +25559,14 @@ function registerMembershipTools(server, ctx, client) {
25562
25559
  );
25563
25560
  server.tool(
25564
25561
  "update_member_role",
25565
- "Update a member's role or profile within the configured organization.",
25562
+ "Change a member's role within the configured organization.",
25566
25563
  {
25567
25564
  userId: external_exports.string().describe("User ID (email address) of the member to update"),
25568
- role: external_exports.enum(["member", "admin", "client"]).optional().describe("New organization role"),
25569
- name: external_exports.string().optional().describe("Updated display name"),
25570
- title: external_exports.string().optional().describe("Updated job title"),
25571
- responsibility: external_exports.string().optional().describe("Updated responsibility description")
25565
+ role: external_exports.enum(["member", "admin", "client"]).describe("New organization role")
25572
25566
  },
25573
- async ({ userId, role, name, title, responsibility }) => {
25567
+ async ({ userId, role }) => {
25574
25568
  try {
25575
- const member = await client.updateOrgMember(ctx.orgId, userId, { role, name, title, responsibility });
25569
+ const member = await client.updateOrgMember(ctx.orgId, userId, { role });
25576
25570
  if (!member) {
25577
25571
  return { content: [{ type: "text", text: `Member not found: "${userId}"` }], isError: true };
25578
25572
  }
@@ -32688,6 +32682,16 @@ function safeParseErrorBody(body) {
32688
32682
  return void 0;
32689
32683
  }
32690
32684
  }
32685
+ var ORG_ROLE_PRECEDENCE = ["admin", "member", "client"];
32686
+ function asLegacyMember(m) {
32687
+ const roles = Array.isArray(m["roles"]) ? m["roles"] : [];
32688
+ const rank = (role2) => {
32689
+ const i = ORG_ROLE_PRECEDENCE.indexOf(role2);
32690
+ return i === -1 ? ORG_ROLE_PRECEDENCE.length : i;
32691
+ };
32692
+ const role = [...roles].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b))[0];
32693
+ return { ...m, role: role ?? "" };
32694
+ }
32691
32695
  function createHttpClient(config2) {
32692
32696
  const url = new URL(config2.apiBaseUrl);
32693
32697
  const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
@@ -32802,21 +32806,37 @@ function createHttpClient(config2) {
32802
32806
  getMembership: async (userId, orgId) => {
32803
32807
  return { userId, orgId, role: "member" };
32804
32808
  },
32805
- // Org membership management
32809
+ // Org membership management — the generic member API, shared with the
32810
+ // teamspace, account and system tiers (rev-4551c900). The org-only routes
32811
+ // this used to call were a second implementation over the same Grant rows
32812
+ // that skipped the lockout rule.
32813
+ //
32814
+ // Those routes also set a name, title and responsibility on the User
32815
+ // record. Nothing does that any more: a name is written on first sign-in
32816
+ // from the identity provider, and the fields are gone from the tool
32817
+ // schemas rather than accepted and silently dropped.
32806
32818
  listOrgMembers: async (orgId) => {
32807
- const result = await request("GET", `/api/organizations/${encodeURIComponent(orgId)}/members`);
32808
- return result ?? [];
32819
+ const result = await request("GET", `/api/orgs/${encodeURIComponent(orgId)}/members`);
32820
+ return (result?.members ?? []).map(asLegacyMember);
32809
32821
  },
32810
32822
  addOrgMember: async (orgId, input) => {
32811
- const result = await request("POST", `/api/organizations/${encodeURIComponent(orgId)}/members`, input);
32812
- return result;
32823
+ const result = await request("POST", `/api/orgs/${encodeURIComponent(orgId)}/members`, {
32824
+ userId: input.email.toLowerCase(),
32825
+ roles: [input.role ?? "member"]
32826
+ });
32827
+ return asLegacyMember(result?.member ?? {});
32813
32828
  },
32814
32829
  updateOrgMember: async (orgId, userId, input) => {
32815
- const result = await request("PUT", `/api/organizations/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`, input);
32816
- return result;
32830
+ if (!input.role) {
32831
+ const current = await request("GET", `/api/orgs/${encodeURIComponent(orgId)}/members`);
32832
+ const found = (current?.members ?? []).find((m) => String(m["userId"]).toLowerCase() === userId.toLowerCase());
32833
+ return found ? asLegacyMember(found) : void 0;
32834
+ }
32835
+ const result = await request("PATCH", `/api/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`, { roles: [input.role] });
32836
+ return asLegacyMember(result?.member ?? {});
32817
32837
  },
32818
32838
  removeOrgMember: async (orgId, userId) => {
32819
- await request("DELETE", `/api/organizations/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`);
32839
+ await request("DELETE", `/api/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`);
32820
32840
  },
32821
32841
  // Both renameOrganization and updateOrganization PATCH the same endpoint.
32822
32842
  // Server-side routing determines which service runs based on which fields are present.
@@ -32846,7 +32866,7 @@ function createHttpClient(config2) {
32846
32866
  try {
32847
32867
  const result = await request(
32848
32868
  "POST",
32849
- `/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-project`,
32869
+ `/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-system`,
32850
32870
  { systemId, targetOrgId }
32851
32871
  );
32852
32872
  const system = result?.system ? {
@@ -32903,7 +32923,11 @@ function createHttpClient(config2) {
32903
32923
  const result = await request("GET", `/api/systems/${encodeURIComponent(systemId)}/facts`);
32904
32924
  return result?.data;
32905
32925
  },
32906
- saveSystemFact: (input) => request("PUT", `/api/systems/${encodeURIComponent(String(input.projectId))}/facts`, input),
32926
+ saveSystemFact: (input) => {
32927
+ const effectiveId = input.systemId ?? input.projectId;
32928
+ if (!effectiveId) throw new Error("saveSystemFact: systemId (or legacy projectId) is required");
32929
+ return request("PUT", `/api/systems/${encodeURIComponent(String(effectiveId))}/facts`, input);
32930
+ },
32907
32931
  transitionSystemLifecycleState: async (systemId, targetState, options) => {
32908
32932
  return request("POST", `/api/systems/${encodeURIComponent(systemId)}/lifecycle/transitions`, {
32909
32933
  targetState,
@@ -33136,12 +33160,15 @@ function createHttpClient(config2) {
33136
33160
  return result?.note ?? result;
33137
33161
  },
33138
33162
  createNote: async (input) => {
33139
- const result = await request("POST", `/api/systems/${encodeURIComponent(input.projectId ?? "")}/notes`, input);
33163
+ const inputRec = input;
33164
+ const effectiveSystemId = inputRec.systemId ?? input.projectId;
33165
+ if (!effectiveSystemId) throw new Error("createNote: systemId (or legacy projectId) is required");
33166
+ const result = await request("POST", `/api/systems/${encodeURIComponent(effectiveSystemId)}/notes`, input);
33140
33167
  return result?.note ?? result;
33141
33168
  },
33142
33169
  updateNote: async (noteId, input) => {
33143
33170
  const existing = await request("GET", `/api/notes/${encodeURIComponent(noteId)}`);
33144
- const systemId = existing?.projectId ?? existing?.note?.projectId;
33171
+ const systemId = existing?.systemId ?? existing?.note?.systemId;
33145
33172
  if (systemId) {
33146
33173
  const result2 = await request("PATCH", `/api/systems/${encodeURIComponent(systemId)}/notes/${encodeURIComponent(noteId)}`, input);
33147
33174
  return result2?.note ?? result2;
@@ -33182,20 +33209,20 @@ function createHttpClient(config2) {
33182
33209
  },
33183
33210
  reassignNote: async (noteId, targetBeatId, reason, _actorId, targetRevisionId) => {
33184
33211
  const note = await request("GET", `/api/notes/${encodeURIComponent(noteId)}`);
33185
- if (!note?.note) return void 0;
33212
+ if (!note?.note?.systemId) return void 0;
33186
33213
  const result = await request(
33187
33214
  "POST",
33188
- `/api/systems/${encodeURIComponent(note.note.projectId)}/notes/${encodeURIComponent(noteId)}/reassign`,
33215
+ `/api/systems/${encodeURIComponent(note.note.systemId)}/notes/${encodeURIComponent(noteId)}/reassign`,
33189
33216
  { targetBeatId, targetRevisionId, reason }
33190
33217
  );
33191
33218
  return result?.note ?? result;
33192
33219
  },
33193
33220
  removeNote: async (noteId, reason, _actorId) => {
33194
33221
  const note = await request("GET", `/api/notes/${encodeURIComponent(noteId)}`);
33195
- if (!note?.note) return void 0;
33222
+ if (!note?.note?.systemId) return void 0;
33196
33223
  const result = await request(
33197
33224
  "POST",
33198
- `/api/systems/${encodeURIComponent(note.note.projectId)}/notes/${encodeURIComponent(noteId)}/remove`,
33225
+ `/api/systems/${encodeURIComponent(note.note.systemId)}/notes/${encodeURIComponent(noteId)}/remove`,
33199
33226
  { reason }
33200
33227
  );
33201
33228
  return result?.note ?? result;
@@ -34145,7 +34172,9 @@ function createHttpClient(config2) {
34145
34172
  ...parentGroupId !== void 0 && { parentGroupId },
34146
34173
  ...createdBy !== void 0 && { createdBy }
34147
34174
  };
34148
- const url2 = input.projectId ? `/api/systems/${encodeURIComponent(input.projectId)}/deliverable-groups` : `/api/teamspaces/${encodeURIComponent(input.teamspaceId)}/deliverable-groups`;
34175
+ const inputRec = input;
34176
+ const effectiveSystemId = inputRec.systemId ?? input.projectId;
34177
+ const url2 = effectiveSystemId ? `/api/systems/${encodeURIComponent(effectiveSystemId)}/deliverable-groups` : `/api/teamspaces/${encodeURIComponent(input.teamspaceId)}/deliverable-groups`;
34149
34178
  const result = await request("POST", url2, body);
34150
34179
  if (result === void 0) throw new Error(`Resource not found when creating DeliverableGroup`);
34151
34180
  return result;
@@ -34965,7 +34994,7 @@ function loadConfig() {
34965
34994
  };
34966
34995
  }
34967
34996
  async function main() {
34968
- console.error(`[harmonica-mcp] v${"4.0.1"} starting\u2026`);
34997
+ console.error(`[harmonica-mcp] v${"4.0.2"} starting\u2026`);
34969
34998
  const config2 = loadConfig();
34970
34999
  const client = createHttpClient({
34971
35000
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "4.0.1",
3
+ "version": "4.0.2",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {