From 8dbbf16f6c3f3682693d7d5bde55dcfaf2b984cb Mon Sep 17 00:00:00 2001 From: Matvii Nesterenko <51422901+kapustazh@users.noreply.github.com> Date: Sun, 13 Sep 2026 09:20:15 +0200 Subject: [PATCH 1/2] feat(web): add MCP profile shortcuts --- .../20260913T071500Z-mcp-profile-docs-copy.md | 80 +++++++++++++++++++ apps/web/src/components/McpProfile.tsx | 19 +++++ apps/web/test/app-composition.test.tsx | 14 +++- 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 .agent/context/20260913T071500Z-mcp-profile-docs-copy.md diff --git a/.agent/context/20260913T071500Z-mcp-profile-docs-copy.md b/.agent/context/20260913T071500Z-mcp-profile-docs-copy.md new file mode 100644 index 0000000..821b9133 --- /dev/null +++ b/.agent/context/20260913T071500Z-mcp-profile-docs-copy.md @@ -0,0 +1,80 @@ +# Session Context: MCP profile docs and bearer copy + +## Date/time + +- UTC: 2026-09-13T07:15:00Z + +## User goal + +Make MCP onboarding easier from Profile by linking directly to `/docs/mcp` and +letting the user copy the newly issued personal bearer token. + +## Original prompt/request + +The user asked to add the documentation link +`https://oneshot.kapustazh.dev/docs/mcp` to Profile and add a button that copies +the bearer token. + +## Assumptions + +- Copying is available only while the plaintext token is already displayed + after generation or rotation. +- The token remains memory-only and is never persisted by the frontend. +- This branch stacks on the deployed Privy Buffer compatibility fix so a new + frontend deployment does not regress that fix. + +## Plan + +1. Add the internal documentation link to the MCP profile panel. +2. Reuse the browser Clipboard API to copy only the displayed bearer. +3. Add focused UI coverage and validate the web package. + +## Key decisions + +- Use `navigator.clipboard` directly; no dependency or storage is needed. +- Report copy failure in the existing profile error surface. + +## Files/components touched + +- `apps/web/src/components/McpProfile.tsx`: documentation link and copy action. +- `apps/web/test/app-composition.test.tsx`: link and clipboard behavior coverage. + +## Commands/checks + +- `pnpm --filter @oneshot/web test -- app-composition.test.tsx` - PASS, + 7 tests. +- `pnpm --filter @oneshot/web typecheck` - PASS. +- `pnpm --filter @oneshot/web test` - PASS, 17 files / 93 tests. +- `pnpm --filter @oneshot/web test:browser` - PASS, 8 Chromium tests. +- `pnpm lint` - PASS. +- `pnpm format:check` - PASS. +- `git diff --check` - PASS. +- Local Node is v22.23.2 while the repository requests v24.19.0. + +## External-doc findings + +- None. + +## Unresolved questions + +- None. + +## Git and PR state + +- Branch: `feat/mcp-profile-docs-copy` +- Base: `fix/privy-browser-buffer` at + `dcc730792440e3f90cb5e96fe0597f80072a51d0` +- Commit: uncommitted +- PR: not created +- CI: not run + +## Review gates + +- Gate A: NOT RUN under the user's standing explicit instruction to continue + without FreePi; no PASS is claimed. +- Gate B: NOT RUN + +## Handoff/next steps + +1. Inspect the focused diff, commit, push, open a stacked PR, and deploy the + frontend. diff --git a/apps/web/src/components/McpProfile.tsx b/apps/web/src/components/McpProfile.tsx index 3f9eda8..6071a43 100644 --- a/apps/web/src/components/McpProfile.tsx +++ b/apps/web/src/components/McpProfile.tsx @@ -24,6 +24,7 @@ function configFor(token: string): string { export function McpProfile(props: { readonly client: JobApiClient }) { const [status, setStatus] = useState(null); const [issued, setIssued] = useState(null); + const [copied, setCopied] = useState(false); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); @@ -36,6 +37,7 @@ export function McpProfile(props: { readonly client: JobApiClient }) { async function issue(): Promise { setBusy(true); + setCopied(false); setError(null); try { const credential = await props.client.issueMcpCredential(status?.configured === true); @@ -52,11 +54,25 @@ export function McpProfile(props: { readonly client: JobApiClient }) { } } + async function copyBearer(): Promise { + try { + if (!issued || !navigator.clipboard) throw new Error('Clipboard is unavailable'); + await navigator.clipboard.writeText(issued.bearer_token); + setCopied(true); + } catch { + setCopied(false); + setError('Could not copy the bearer token.'); + } + } + return (

PROFILE / AGENT ACCESS

Connect your agent

Your bearer is bound to this Privy account and its private request workspace.

+

+ Open MCP documentation +

Request key: {issued.request_key}

diff --git a/apps/web/test/app-composition.test.tsx b/apps/web/test/app-composition.test.tsx index 4d0d6cb..93d239d 100644 --- a/apps/web/test/app-composition.test.tsx +++ b/apps/web/test/app-composition.test.tsx @@ -5,14 +5,17 @@ import { } from '@oneshot/settlement-ui'; import { cleanup, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { afterEach, describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { App } from '../src/App.js'; import { OneShotApiClient } from '../src/api/client.js'; import type { JobApiClient } from '../src/api/job-client.js'; import { signedInSession } from './support/fake-session.js'; -afterEach(cleanup); +afterEach(() => { + vi.restoreAllMocks(); + cleanup(); +}); describe('Gate P5 shell composition', () => { it('separates the public landing page from the authenticated cabinet route', () => { @@ -106,6 +109,7 @@ describe('Gate P5 shell composition', () => { it('generates a personal MCP bearer in Profile', async () => { const user = userEvent.setup(); + const writeText = vi.spyOn(navigator.clipboard, 'writeText').mockResolvedValue(); const jobClient = { async mcpCredentialStatus() { return { configured: false, request_key: 'profile-request' }; @@ -129,10 +133,16 @@ describe('Gate P5 shell composition', () => { ); await user.click(screen.getByRole('tab', { name: 'Profile' })); + expect(screen.getByRole('link', { name: 'Open MCP documentation' }).getAttribute('href')).toBe( + '/docs/mcp', + ); await user.click(await screen.findByRole('button', { name: 'Generate bearer token' })); expect( (await screen.findByLabelText('Personal MCP client configuration')).textContent, ).toContain('Bearer personal-secret-token'); + await user.click(screen.getByRole('button', { name: 'Copy bearer token' })); + expect(writeText).toHaveBeenCalledWith('personal-secret-token'); + expect(screen.getByRole('button', { name: 'Bearer copied' })).toBeTruthy(); expect(screen.getByText('profile-request')).toBeTruthy(); }); From 7bde99b5cbc577fab00a73ec00bd0164a9d1e7ec Mon Sep 17 00:00:00 2001 From: Matvii Nesterenko <51422901+kapustazh@users.noreply.github.com> Date: Sun, 13 Sep 2026 09:23:27 +0200 Subject: [PATCH 2/2] docs(context): record MCP profile rollout --- .../20260913T071500Z-mcp-profile-docs-copy.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.agent/context/20260913T071500Z-mcp-profile-docs-copy.md b/.agent/context/20260913T071500Z-mcp-profile-docs-copy.md index 821b9133..3e18f24 100644 --- a/.agent/context/20260913T071500Z-mcp-profile-docs-copy.md +++ b/.agent/context/20260913T071500Z-mcp-profile-docs-copy.md @@ -64,9 +64,14 @@ the bearer token. - Branch: `feat/mcp-profile-docs-copy` - Base: `fix/privy-browser-buffer` at `dcc730792440e3f90cb5e96fe0597f80072a51d0` -- Commit: uncommitted -- PR: not created -- CI: not run +- Implementation commit: `8dbbf16f6c3f3682693d7d5bde55dcfaf2b984cb` +- PR: [#127](https://github.com/SWOFART/OneShot/pull/127), draft, stacked + on `fix/privy-browser-buffer` +- CI: repository policy, Markdown/Mermaid, frontend browser acceptance, and + Cloudflare Workers build passed; ESLint/TypeScript was pending at this snapshot. +- Production frontend: Cloudflare Worker version + `11981c12-6a81-4e4a-9ec9-819d6b52d458`; the public entry bundle contains the + documentation link, bearer-copy action, and the preceding Buffer polyfill. ## Review gates @@ -76,5 +81,5 @@ the bearer token. ## Handoff/next steps -1. Inspect the focused diff, commit, push, open a stacked PR, and deploy the - frontend. +1. Wait for the final PR check and leave both stacked PRs for human review; + agents do not merge.