From 806c175af7c5be93690a221198d8ac6289a96d32 Mon Sep 17 00:00:00 2001 From: Ernest Koe Date: Tue, 24 Mar 2026 11:27:44 -0400 Subject: [PATCH] feat: auto-resolve organization ID in MCP server The MCP label_conversation and assign_conversation tools required an organization parameter that Claude Desktop didn't reliably provide, causing Missive API 400 errors. Now the org ID is lazily fetched from GET /organizations and cached, making the parameter optional. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/__tests__/client.test.ts | 13 +++++++++++++ src/client.ts | 7 +++++++ src/mcp.ts | 24 ++++++++++++++++++++---- src/types.ts | 5 +++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index cbf1573..b4e6026 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -377,4 +377,17 @@ describe("MissiveClient", () => { expect(body.posts.organization).toBe("org-1"); }); }); + + describe("listOrganizations", () => { + it("calls GET /organizations", async () => { + const fetch = mockFetch({ organizations: [{ id: "org-1", name: "Test Org" }] }); + vi.stubGlobal("fetch", fetch); + const result = await client.listOrganizations(); + expect(fetch).toHaveBeenCalledWith( + "https://api.test.com/v1/organizations", + expect.objectContaining({ method: "GET" }), + ); + expect(result.organizations).toEqual([{ id: "org-1", name: "Test Org" }]); + }); + }); }); diff --git a/src/client.ts b/src/client.ts index 2e6045d..8c03f7e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -9,6 +9,7 @@ import type { ContactBook, CreateContactParams, UpdateContactParams, + Organization, } from "./types.js"; export class MissiveApiError extends Error { @@ -210,6 +211,12 @@ export class MissiveClient { }); } + // --- Organizations --- + + async listOrganizations(): Promise<{ organizations: Organization[] }> { + return this.request("GET", "/organizations"); + } + // --- Contact Books --- async listContactBooks(params?: { diff --git a/src/mcp.ts b/src/mcp.ts index 2da6948..fa743a2 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -24,6 +24,20 @@ function err(e: unknown): { content: Array<{ type: "text"; text: string }>; isEr return { content: [{ type: "text" as const, text: String(e) }], isError: true }; } +// --- Organization resolution --- + +let cachedOrgId: string | undefined; + +async function resolveOrgId(explicit?: string): Promise { + if (explicit) return explicit; + if (cachedOrgId) return cachedOrgId; + const { organizations } = await client.listOrganizations(); + if (organizations.length === 0) throw new Error("No organizations found"); + if (organizations.length > 1) throw new Error("Multiple organizations found — provide organization ID explicitly"); + cachedOrgId = organizations[0].id; + return cachedOrgId; +} + // --- Server --- const server = new McpServer({ @@ -121,13 +135,14 @@ server.registerTool("assign_conversation", { inputSchema: { id: z.string().describe("Conversation ID"), users: z.array(z.string()).describe("User IDs to assign"), - organization: z.string().describe("Organization ID (required by Missive API)"), + organization: z.string().optional().describe("Organization ID (auto-resolved if omitted)"), }, }, async ({ id, users, organization }) => { try { + const orgId = await resolveOrgId(organization); return ok(await client.performConversationAction(id, { add_assignees: users, - organization, + organization: orgId, })); } catch (e) { return err(e); } }); @@ -137,14 +152,15 @@ server.registerTool("label_conversation", { description: "Add or remove shared labels on a conversation.", inputSchema: { id: z.string().describe("Conversation ID"), - organization: z.string().describe("Organization ID (required by Missive API)"), + organization: z.string().optional().describe("Organization ID (auto-resolved if omitted)"), add: z.array(z.string()).optional().describe("Shared label IDs to add"), remove: z.array(z.string()).optional().describe("Shared label IDs to remove"), }, }, async ({ id, organization, add, remove }) => { try { + const orgId = await resolveOrgId(organization); return ok(await client.performConversationAction(id, { - organization, + organization: orgId, add_shared_labels: add, remove_shared_labels: remove, })); diff --git a/src/types.ts b/src/types.ts index 081e51b..3f88839 100644 --- a/src/types.ts +++ b/src/types.ts @@ -142,6 +142,11 @@ export interface UpdateContactParams { infos?: ContactInfo[]; } +export interface Organization { + id: string; + name: string; +} + export interface ConversationActionParams { close?: boolean; add_to_inbox?: boolean;