fix(mcp): clarify missing-workspace and admin-key errors
This commit is contained in:
parent
0208f0dd2b
commit
a66a09af54
|
|
@ -47,15 +47,16 @@ export function parseConfig(request: Request, env: Env = {}): HonchoConfig {
|
|||
};
|
||||
}
|
||||
|
||||
export const MISSING_WORKSPACE_ID_MESSAGE =
|
||||
"Missing workspace_id. Pass workspace_id on the next tool call, or set the X-Honcho-Workspace-ID header on the connection so it is used automatically.";
|
||||
|
||||
export function resolveWorkspaceId(
|
||||
config: HonchoConfig,
|
||||
workspaceId?: string,
|
||||
): string {
|
||||
const id = workspaceId?.trim() || config.workspaceId?.trim();
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
"workspace_id is required. Pass it as a tool argument or set the X-Honcho-Workspace-ID header.",
|
||||
);
|
||||
throw new Error(MISSING_WORKSPACE_ID_MESSAGE);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import { z } from "zod";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import type { PageResponse, WorkspaceResponse } from "@honcho-ai/sdk";
|
||||
import {
|
||||
HonchoError,
|
||||
type PageResponse,
|
||||
type WorkspaceResponse,
|
||||
} from "@honcho-ai/sdk";
|
||||
import type { ToolContext } from "../types.js";
|
||||
import { resolveWorkspaceId } from "../config.js";
|
||||
import {
|
||||
textResult,
|
||||
errorResult,
|
||||
|
|
@ -9,6 +14,14 @@ import {
|
|||
workspaceIdSchema,
|
||||
} from "../types.js";
|
||||
|
||||
function withAdminKeyHint(prefix: string, e: unknown): string {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
const denied =
|
||||
e instanceof HonchoError && (e.status === 401 || e.status === 403);
|
||||
if (!denied) return `${prefix}: ${message}`;
|
||||
return `${prefix}: ${message}. This operation is only possible with an admin API key.`;
|
||||
}
|
||||
|
||||
function formatWorkspace(workspace: WorkspaceResponse) {
|
||||
return {
|
||||
id: workspace.id,
|
||||
|
|
@ -100,9 +113,7 @@ export function register(server: McpServer, ctx: ToolContext) {
|
|||
pages: result.pages,
|
||||
});
|
||||
} catch (e) {
|
||||
return errorResult(
|
||||
`Failed to list workspaces: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
return errorResult(withAdminKeyHint("Failed to list workspaces", e));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
@ -130,20 +141,19 @@ export function register(server: McpServer, ctx: ToolContext) {
|
|||
},
|
||||
async ({ workspace_id, metadata }) => {
|
||||
try {
|
||||
const id = resolveWorkspaceId(ctx.config, workspace_id);
|
||||
const workspace = await ctx.unscoped.http.post<WorkspaceResponse>(
|
||||
"/v3/workspaces",
|
||||
{
|
||||
body: {
|
||||
id: workspace_id,
|
||||
id,
|
||||
metadata,
|
||||
},
|
||||
},
|
||||
);
|
||||
return textResult(formatWorkspace(workspace));
|
||||
} catch (e) {
|
||||
return errorResult(
|
||||
`Failed to create workspace: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
return errorResult(withAdminKeyHint("Failed to create workspace", e));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,14 +11,17 @@ export interface ToolContext {
|
|||
unscoped: Honcho;
|
||||
}
|
||||
|
||||
/** Required unless X-Honcho-Workspace-ID is set, in which case that value is the default. */
|
||||
/**
|
||||
* Always optional at the schema layer so a missing value reaches clientFor,
|
||||
* which returns a clear error (header or workspace_id on the next call).
|
||||
*/
|
||||
export function workspaceIdSchema(ctx: ToolContext) {
|
||||
const fromHeader = ctx.config.workspaceId;
|
||||
const description = fromHeader
|
||||
? `Workspace to operate in. The connection already set X-Honcho-Workspace-ID=${fromHeader}; omit this argument unless you need a different workspace.`
|
||||
: "Workspace to operate in. Prefer the client setting X-Honcho-Workspace-ID on the connection — then you can omit this on every call. Only pass it (or use list_workspaces / create_workspace) when the header is unset.";
|
||||
const base = z.string().min(1).describe(description);
|
||||
return fromHeader ? base.optional().default(fromHeader) : base;
|
||||
const field = z.string().optional().describe(description);
|
||||
return fromHeader ? field.default(fromHeader) : field;
|
||||
}
|
||||
|
||||
export function textResult(
|
||||
|
|
|
|||
Loading…
Reference in New Issue