{active.map(row => row.clientName).join(", ")}Connected as you
diff --git a/ui/storybook/fixtures/publicMcp.ts b/ui/storybook/fixtures/publicMcp.ts
index 8abf95a6f0..49a3b716e7 100644
--- a/ui/storybook/fixtures/publicMcp.ts
+++ b/ui/storybook/fixtures/publicMcp.ts
@@ -18,6 +18,7 @@ export interface PublicMcpFixture {
request?: Partial;
loading?: boolean;
unavailable?: boolean;
+ connectionsUnavailable?: boolean;
pending?: boolean;
mutationError?: boolean;
empty?: boolean;
@@ -46,7 +47,7 @@ export function installPublicMcpFixture(fixture: PublicMcpFixture = {}) {
if (fixture.unavailable) return error("Assistant connections are unavailable. Check the experimental setting or reconnect.", 403);
if (path === "/api/instance/settings/experimental") return Response.json(settings);
if (path === "/api/mcp/setup") return Response.json({ enabled: settings.enablePublicMcp, serverUrl: "https://paperclip.example/mcp/paperclip" });
- if (path === "/api/mcp/connections") return Response.json(rows);
+ if (path === "/api/mcp/connections") return fixture.connectionsUnavailable ? error("Connection status is unavailable.") : Response.json(rows);
if (path === `/api/mcp/requests/${request.id}`) return Response.json({ ...request, ...fixture.request });
} else {
if (fixture.pending) return new Promise(() => {});
diff --git a/ui/storybook/stories/public-mcp-consent.stories.tsx b/ui/storybook/stories/public-mcp-consent.stories.tsx
index 9add31c92b..786b4c0316 100644
--- a/ui/storybook/stories/public-mcp-consent.stories.tsx
+++ b/ui/storybook/stories/public-mcp-consent.stories.tsx
@@ -18,6 +18,7 @@ const chooseOrganization: NonNullable = async ({ canvasElement })
await userEvent.click(await c.findByRole("radio", { name: "Acme Research" }));
};
export const ChooseOrganization: Story = {};
+export const OpenCodeOrganization: Story = { parameters: { fixture: { request: { clientName: "OpenCode", redirectOrigin: "http://127.0.0.1:19876", companies: [{ ...request.companies[0], name: "Paperclip Storybook" }] } } } };
export const AllowDelegation: Story = { play: async context => {
await chooseOrganization(context);
const c = within(context.canvasElement);
diff --git a/ui/storybook/stories/public-mcp-journey.stories.tsx b/ui/storybook/stories/public-mcp-journey.stories.tsx
index 349e4dd9a6..5cc5a6ac1a 100644
--- a/ui/storybook/stories/public-mcp-journey.stories.tsx
+++ b/ui/storybook/stories/public-mcp-journey.stories.tsx
@@ -6,6 +6,7 @@ import { Link, Route, Routes } from "@/lib/router";
import { Browse } from "@/pages/apps/Browse";
import { AssistantConnection } from "@/pages/apps/AssistantConnection";
import { InstanceExperimentalSettings } from "@/pages/InstanceExperimentalSettings";
+import { PublicMcpPresenter } from "./public-mcp-presenter";
import { installPublicMcpFixture } from "../fixtures/publicMcp";
function ConnectionJourney({ assistant = "opencode" }: { assistant?: "codex" | "claude" | "opencode" | "other" }) {
@@ -30,7 +31,10 @@ const meta = {
} satisfies Meta;
export default meta;
type Story = StoryObj;
-export const GuidedWalkthrough: Story = {};
+export const GuidedWalkthrough: Story = { render: () => };
+export const Connections: Story = {};
+export const EnableSetup: Story = { parameters: { initialEntries: ["/PAP/apps/assistant-connection"] } };
+export const ConnectionStatusUnavailable: Story = { parameters: { fixture: { enabled: true, connectionsUnavailable: true } } };
export const OpenCodeSetup: Story = { parameters: { initialEntries: ["/PAP/apps/assistant-connection"], fixture: { enabled: true, empty: true } } };
export const CodexSetup: Story = { ...OpenCodeSetup, args: { assistant: "codex" } };
export const ClaudeSetup: Story = { ...OpenCodeSetup, args: { assistant: "claude" } };
diff --git a/ui/storybook/stories/public-mcp-presenter.tsx b/ui/storybook/stories/public-mcp-presenter.tsx
new file mode 100644
index 0000000000..f4bd82d161
--- /dev/null
+++ b/ui/storybook/stories/public-mcp-presenter.tsx
@@ -0,0 +1,40 @@
+import { useState } from "react";
+import { Button } from "@/components/ui/button";
+
+const chapters = [
+ { title: "Start inside Paperclip", place: "Paperclip → Connectors", action: "You are already in your organization. Open Connectors in the sidebar and find Assistant Connection (MCP). Click Set up.", result: "This connects an outside assistant to Paperclip using your human account. There is no agent picker.", story: "assistant-connections-start-here--connections" },
+ { title: "Enable the experiment", place: "Setup → Experimental settings → Setup", action: "When access is off, follow Open Experimental settings. Enable Assistant connections (MCP), then use the Set up an assistant connection link beside the setting to return.", result: "Enabling makes connection setup available. No assistant is authorized yet.", story: "assistant-connections-start-here--enable-setup" },
+ { title: "Configure and sign in from OpenCode", place: "Paperclip setup → your terminal", action: "Choose OpenCode. Merge the displayed entry into opencode.json, then run opencode mcp auth paperclip from that directory. That command opens browser sign-in and consent. The server URL and command are both supplied here.", result: "The assistant creates the authorization request and owns its callback. Paperclip does not provide a reusable consent URL. These commands are instructions in Storybook; it does not run your terminal.", story: "assistant-connections-start-here--open-code-setup" },
+ { title: "Approve your organization", place: "Browser sign-in → Paperclip consent", action: "Sign in if needed, select your organization, review write access, and click Connect organization. The browser returns authorization to OpenCode. The preview below is a fixture and issues no credentials.", result: "The grant uses your permissions in this organization. Selecting an agent to receive work later does not change your identity.", story: "assistant-connections-consent--open-code-organization" },
+ { title: "Review your organization in OpenCode", place: "Terminal → opencode web → new conversation", action: "After sign-in completes, start opencode web in the same directory. Ask which Paperclip organization is connected and which agents and tasks it has.", result: "The assistant should read live organization and agent data before suggesting an assignee.", scene: ["Example request: “Which Paperclip organization am I connected to? Show me its agents and open tasks.”", "Expected behavior: use the connection, agent-list, and task-search tools. This is an example, not a captured OpenCode response."] },
+ { title: "Delegate a task", place: "OpenCode → Paperclip task", action: "Ask an available agent to produce a small result, such as three names for a neighborhood plant nursery. Ask for the task link.", result: "One durable task is created as you. Paperclip schedules the agent with its configured credentials, environment, budget, and approvals. A created task is not proof that work has completed.", scene: ["Example request: “Have our researcher propose three nursery names, explain each briefly, and save a report on the task.”", "Expected behavior: return a durable task reference and disclose the scheduling effect. No task is created by this Storybook panel."] },
+ { title: "Return for results and feedback", place: "A later OpenCode conversation", action: "Open a new conversation, find the task, and ask for its saved report. Add feedback as yourself if write access was approved.", result: "The task and report persist outside the original chat. Feedback can wake the agent and use the organization’s execution budget.", scene: ["Example request: “Find my nursery naming task and summarize the completed report.”", "Example feedback: “Add my comment: Meadow & Root is my favorite.”", "Expected behavior: read current progress and the stored document, then attribute any requested comment to the connected person. These are illustrative prompts, not live results."] },
+ { title: "Return to Connections", place: "Paperclip → Connectors → Assistant Connection (MCP)", action: "Open the same catalog entry to inspect your connected assistants and their read/write access. Revoke OpenCode if you want to stop future calls.", result: "The list shows your actual grants for the selected organization. Revocation does not cancel work already delegated. This checkpoint uses sample grant data.", story: "assistant-connections-start-here--connected" },
+];
+
+/** Presenter controls stay outside the actual product iframe. */
+export function PublicMcpPresenter() {
+ const [index, setIndex] = useState(0);
+ const chapter = chapters[index]!;
+ return
+
+
STORYBOOK WALKTHROUGH · SAMPLE DATA
+
Start in Paperclip. Bring your organization into your assistant.
+
Click through the real product screens below. Presenter chapters explain the terminal and assistant handoffs; they do not simulate live OAuth or model execution.