fix(mcp): cover assistant setup integration contracts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-05 17:12:17 -05:00
1 parent c1f1342a96
commit 797fecd803
3 files changed
+31 -6

No files matched your search

+11 -1
View File
@@ -1666,7 +1666,7 @@ function resolveOperationAuthLevel(
): OpenApiAuthLevel {
const key = operationKey(method, path);
if (key === "GET /api/mcp/requests/{id}") return "public";
if (path.startsWith("/api/mcp/requests/") || path.startsWith("/api/mcp/connections")) return "board";
if (path === "/api/mcp/setup" || path.startsWith("/api/mcp/requests/") || path.startsWith("/api/mcp/connections")) return "board";
if (PUBLIC_OPERATIONS.has(key)) return "public";
if (key === "POST /api/mcp/project-tools" || key === "POST /api/companies/{companyId}/slack/tasks/{issueId}/tools") return "agent_run";
if (RUNTIME_TOOLS_OPERATIONS.has(key)) return "runtime_tools";
@@ -11334,6 +11334,16 @@ registerCurrentRoute({
},
});
registerCurrentRoute({
method: "get", path: "/api/mcp/setup", tags: ["tool-gateway"],
summary: "Read assistant connection setup for a signed-in person",
description: "Returns the live experimental setting and canonical MCP server URL. Requires a human browser session; agent and board API keys are not accepted. Remains available while the experiment is disabled and grants no access.",
responses: {
200: r.ok(z.object({ enabled: z.boolean(), serverUrl: z.string().url() })),
401: r.unauthorized, 403: r.forbidden, 404: r.notFound,
},
});
registerCurrentRoute({
method: "get", path: "/api/mcp/requests/{id}", tags: ["tool-gateway"],
summary: "Describe an assistant connection request and available sign-in options",
@@ -1,6 +1,7 @@
// @vitest-environment jsdom
import { flushSync } from "react-dom";
import { MemoryRouter } from "react-router-dom";
import { createRoot, type Root } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { INSTANCE_FEATURE_KEYS } from "@paperclipai/shared";
@@ -21,6 +22,10 @@ vi.mock("@/api/instanceSettings", () => ({
instanceSettingsApi: mockInstanceSettingsApi,
}));
vi.mock("@/context/CompanyContext", () => ({
useCompany: () => ({ selectedCompany: { id: "butter", issuePrefix: "BUT" } }),
}));
vi.mock("../context/BreadcrumbContext", () => ({
useBreadcrumbs: () => ({ setBreadcrumbs: vi.fn() }),
}));
@@ -165,7 +170,9 @@ describe("InstanceExperimentalSettings — Conference Room Chat card (PAP-11233)
flushSync(() => {
root!.render(
<QueryClientProvider client={queryClient}>
<InstanceExperimentalSettings />
<MemoryRouter initialEntries={["/BUT/company/settings/instance/experimental"]}>
<InstanceExperimentalSettings />
</MemoryRouter>
</QueryClientProvider>,
);
});
@@ -242,6 +249,7 @@ describe("InstanceExperimentalSettings — Conference Room Chat card (PAP-11233)
await flushReact();
expect(mockInstanceSettingsApi.updateExperimental).toHaveBeenLastCalledWith({ enablePublicMcp: enabled });
expect(container.querySelector(selector)?.getAttribute("aria-checked")).toBe(String(enabled));
expect(Boolean(container.querySelector('a[href="/BUT/apps/assistant-connection"]'))).toBe(enabled);
}
});
@@ -784,7 +792,9 @@ describe("InstanceExperimentalSettings — cloud-managed keys", () => {
flushSync(() => {
root!.render(
<QueryClientProvider client={queryClient}>
<InstanceExperimentalSettings />
<MemoryRouter initialEntries={["/BUT/company/settings/instance/experimental"]}>
<InstanceExperimentalSettings />
</MemoryRouter>
</QueryClientProvider>,
);
});
@@ -937,7 +947,9 @@ describe("InstanceExperimentalSettings — card ordering and headings (PAP-393)"
flushSync(() => {
root!.render(
<QueryClientProvider client={queryClient}>
<InstanceExperimentalSettings />
<MemoryRouter initialEntries={["/BUT/company/settings/instance/experimental"]}>
<InstanceExperimentalSettings />
</MemoryRouter>
</QueryClientProvider>,
);
});
@@ -1052,7 +1064,9 @@ describe("InstanceExperimentalSettings — operator-hidden cards", () => {
flushSync(() => {
root!.render(
<QueryClientProvider client={queryClient}>
<InstanceExperimentalSettings />
<MemoryRouter initialEntries={["/BUT/company/settings/instance/experimental"]}>
<InstanceExperimentalSettings />
</MemoryRouter>
</QueryClientProvider>,
);
});
+2 -1
View File
@@ -6,6 +6,7 @@ import { ApiError } from "@/api/client";
import { useCompany } from "@/context/CompanyContext";
import { useBreadcrumbs } from "@/context/BreadcrumbContext";
import { Link } from "@/lib/router";
import { copyTextToClipboard } from "@/lib/clipboard";
import { CompanyPatternIcon } from "@/components/CompanyPatternIcon";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -60,7 +61,7 @@ function CopyValue({ value, label }: { value: string; label: string }) {
<div className="flex items-start gap-3 rounded-md border border-border bg-muted/30 p-3">
<pre className="min-w-0 flex-1 overflow-x-auto whitespace-pre-wrap break-words font-mono text-xs leading-relaxed">{value}</pre>
<Button type="button" variant="ghost" size="icon" className="size-8 shrink-0" aria-label={`Copy ${label}`} onClick={async () => {
try { await navigator.clipboard.writeText(value); setCopied(true); setError(false); }
try { await copyTextToClipboard(value); setCopied(true); setError(false); }
catch { setError(true); }
}}>{copied ? <Check className="size-4" /> : <Copy className="size-4" />}</Button>
</div>