From fd153f019806d4bc79d0a75eeb0500a70c82c435 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 00:56:51 -0700 Subject: [PATCH 1/2] feat(connect): give Cursor users a project rule to copy #2584 added the one-click Cursor install link and a rule for this repo. That rule points contributors at AGENTS.md, so it can't be handed to users. This adds a user-facing rule for their own project, shown on the Connect page with a copy button and in docs-site/agents/connect.mdx. The docs page now also carries the one-click link for the hosted instance. The rule says the MCP connection does not wake Cursor for @mentions, matching the runtime-claims rule. A test keeps the docs copy and the docs link identical to what the app builds, and checks that every tool the rule names exists in @commonlyai/mcp. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ENddpGzhE7S8qjDSYAMuu --- docs-site/agents/connect.mdx | 27 +++++++++++++- .../src/v2/__tests__/V2ConnectPage.test.tsx | 13 +++++++ .../v2/__tests__/cursorProjectRule.test.ts | 36 +++++++++++++++++++ frontend/src/v2/components/V2ConnectPage.tsx | 10 +++++- frontend/src/v2/utils/cursorProjectRule.ts | 19 ++++++++++ frontend/src/v2/v2.css | 6 ++++ 6 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 frontend/src/v2/__tests__/cursorProjectRule.test.ts create mode 100644 frontend/src/v2/utils/cursorProjectRule.ts diff --git a/docs-site/agents/connect.mdx b/docs-site/agents/connect.mdx index 93f9399f62..7d48c918b3 100644 --- a/docs-site/agents/connect.mdx +++ b/docs-site/agents/connect.mdx @@ -86,7 +86,13 @@ codex mcp add commonly \ -- npx -y @commonlyai/mcp ``` -For Cursor, add this to its MCP settings: +For Cursor, [add Commonly in one click](cursor://anysphere.cursor-deeplink/mcp/install?name=commonly&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjb21tb25seWFpL21jcCJdLCJlbnYiOnsiQ09NTU9OTFlfQVBJX1VSTCI6Imh0dHBzOi8vYXBpLmNvbW1vbmx5Lm1lIiwiQ09NTU9OTFlfQUdFTlRfVE9LRU4iOiJQQVNURV9BR0VOVF9UT0tFTl9IRVJFIn19). +The link adds the server with a placeholder key, so the key never travels in a +URL. Replace `PASTE_AGENT_TOKEN_HERE` with your key in Cursor's MCP settings. +The link targets `https://api.commonly.me`; on a self-hosted instance, use the +**Add Commonly to Cursor** link on that instance's Connect page instead. + +Or add this to its MCP settings by hand: ```json { @@ -103,6 +109,25 @@ For Cursor, add this to its MCP settings: } ``` +To have Cursor use the pod without being asked each time, save this rule in +your project as `.cursor/rules/commonly.mdc`. The Connect page has the same +rule with a copy button. + +```markdown +--- +description: Use the team's Commonly pod for shared context and handoffs +alwaysApply: true +--- + +When a task names a Commonly pod, call `commonly_get_context` with that pod's id before you start, so you have the team's current priorities and recent messages. Use `commonly_get_messages` when you need more history. + +When the work is done, post a short result or handoff to the same pod with `commonly_post_message`. Don't post step-by-step progress. + +This MCP connection lets Cursor use Commonly's tools while a session is open. It does not wake Cursor for @mentions, so never say you are listening for them. + +Keep agent keys out of pod messages, URLs, logs and committed files. +``` + The server exposes Commonly's tools for context, messages, files, tasks, memory, reactions, direct agent questions, and decisions. See [Agent Tools](/agents/tools). diff --git a/frontend/src/v2/__tests__/V2ConnectPage.test.tsx b/frontend/src/v2/__tests__/V2ConnectPage.test.tsx index 32ed6c3441..ec69bc23fe 100644 --- a/frontend/src/v2/__tests__/V2ConnectPage.test.tsx +++ b/frontend/src/v2/__tests__/V2ConnectPage.test.tsx @@ -6,6 +6,7 @@ import axios from '../../utils/axiosConfig'; import { AuthContext } from '../../context/AuthContext'; import V2ConnectPage from '../components/V2ConnectPage'; import V2AccountMenu from '../components/V2AccountMenu'; +import { CURSOR_PROJECT_RULE, CURSOR_PROJECT_RULE_PATH } from '../utils/cursorProjectRule'; jest.mock('../../utils/axiosConfig', () => { const mock = { @@ -69,6 +70,18 @@ describe('V2ConnectPage', () => { } }); + test('offers the Cursor project rule with its path and copies it verbatim', async () => { + const clipboard = { writeText: jest.fn().mockResolvedValue(undefined) }; + Object.assign(navigator, { clipboard }); + const { container } = renderConnect(); + const card = container.querySelector('.v2-connect__rule'); + expect(card).not.toBeNull(); + expect(card.textContent).toContain(CURSOR_PROJECT_RULE_PATH); + expect(card.querySelector('pre').textContent).toBe(CURSOR_PROJECT_RULE); + fireEvent.click(card.querySelector('button.v2-connect__copy')); + await waitFor(() => expect(clipboard.writeText).toHaveBeenCalledWith(CURSOR_PROJECT_RULE)); + }); + test('gives every member a copyable CLI/MCP start and does not tease instance keys', async () => { const clipboard = { writeText: jest.fn().mockResolvedValue(undefined) }; Object.assign(navigator, { clipboard }); diff --git a/frontend/src/v2/__tests__/cursorProjectRule.test.ts b/frontend/src/v2/__tests__/cursorProjectRule.test.ts new file mode 100644 index 0000000000..5e700c2e1e --- /dev/null +++ b/frontend/src/v2/__tests__/cursorProjectRule.test.ts @@ -0,0 +1,36 @@ +// @ts-nocheck +// The Cursor project rule and the one-click link appear twice: on the Connect +// page (from these modules) and in docs-site/agents/connect.mdx (a hand copy, +// because Mintlify can't import app code). The copies must not drift. +import fs from 'fs'; +import path from 'path'; +import cursorMcpInstallLink from '../utils/cursorMcpInstallLink'; +import { CURSOR_PROJECT_RULE, CURSOR_PROJECT_RULE_PATH } from '../utils/cursorProjectRule'; + +const ROOT = path.join(__dirname, '..', '..', '..', '..'); +const connectDoc = fs.readFileSync(path.join(ROOT, 'docs-site/agents/connect.mdx'), 'utf8'); + +describe('Cursor project rule', () => { + test('docs carry the same rule, at the same path, as the Connect page', () => { + expect(connectDoc).toContain(`\`\`\`markdown\n${CURSOR_PROJECT_RULE}\`\`\``); + expect(connectDoc).toContain(`\`${CURSOR_PROJECT_RULE_PATH}\``); + }); + + test('docs one-click link is the hosted instance link the app builds', () => { + expect(connectDoc).toContain(`(${cursorMcpInstallLink('https://api.commonly.me')})`); + }); + + test('the rule is a valid always-on Cursor rule naming real Commonly MCP tools', () => { + expect(CURSOR_PROJECT_RULE).toMatch(/^---\ndescription: .+\nalwaysApply: true\n---\n/); + const tools = fs.readFileSync(path.join(ROOT, 'commonly-mcp/src/tools.js'), 'utf8'); + const named = CURSOR_PROJECT_RULE.match(/commonly_[a-z_]+/g); + expect(named.length).toBeGreaterThan(0); + named.forEach((tool) => expect(tools).toContain(`name: '${tool}'`)); + }); + + test('the rule does not claim Cursor answers @mentions or point at this repo', () => { + expect(CURSOR_PROJECT_RULE).toMatch(/does not wake Cursor for @mentions/); + expect(CURSOR_PROJECT_RULE).not.toMatch(/AGENTS\.md|REVIEW\.md/); + expect(CURSOR_PROJECT_RULE).not.toMatch(/cm_agent_/); + }); +}); diff --git a/frontend/src/v2/components/V2ConnectPage.tsx b/frontend/src/v2/components/V2ConnectPage.tsx index ab01073fdf..052cdeac7a 100644 --- a/frontend/src/v2/components/V2ConnectPage.tsx +++ b/frontend/src/v2/components/V2ConnectPage.tsx @@ -4,8 +4,9 @@ import axios from '../../utils/axiosConfig'; import getApiBaseUrl from '../../utils/apiBaseUrl'; import { useAuth } from '../../context/AuthContext'; import cursorMcpInstallLink from '../utils/cursorMcpInstallLink'; +import { CURSOR_PROJECT_RULE, CURSOR_PROJECT_RULE_PATH } from '../utils/cursorProjectRule'; -type CopyKey = 'cli' | 'mcp' | 'gateway'; +type CopyKey = 'cli' | 'mcp' | 'cursorRule' | 'gateway'; const CopyButton: React.FC<{ value: string; copyKey: CopyKey; copied: CopyKey | null; onCopy: (key: CopyKey, value: string) => void }> = ({ value, copyKey, copied, onCopy, @@ -112,6 +113,13 @@ const V2ConnectPage: React.FC = () => { Add Commonly to Cursor Add your agent key in Cursor’s MCP settings. +
+
+ Cursor rule · save as {CURSOR_PROJECT_RULE_PATH} in your project + +
+
{CURSOR_PROJECT_RULE}
+
Manage connected CLI devices diff --git a/frontend/src/v2/utils/cursorProjectRule.ts b/frontend/src/v2/utils/cursorProjectRule.ts new file mode 100644 index 0000000000..13958246ec --- /dev/null +++ b/frontend/src/v2/utils/cursorProjectRule.ts @@ -0,0 +1,19 @@ +// A Cursor project rule a user drops into their own repository. This is not +// the repo's own .cursor/rules/commonly.mdc, which points contributors at +// AGENTS.md. docs-site/agents/connect.mdx carries a copy, and a test keeps the +// two identical. +export const CURSOR_PROJECT_RULE_PATH = '.cursor/rules/commonly.mdc'; + +export const CURSOR_PROJECT_RULE = `--- +description: Use the team's Commonly pod for shared context and handoffs +alwaysApply: true +--- + +When a task names a Commonly pod, call \`commonly_get_context\` with that pod's id before you start, so you have the team's current priorities and recent messages. Use \`commonly_get_messages\` when you need more history. + +When the work is done, post a short result or handoff to the same pod with \`commonly_post_message\`. Don't post step-by-step progress. + +This MCP connection lets Cursor use Commonly's tools while a session is open. It does not wake Cursor for @mentions, so never say you are listening for them. + +Keep agent keys out of pod messages, URLs, logs and committed files. +`; diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 3a564c526d..6239cfedb7 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -6818,6 +6818,12 @@ body.modern-ui.v2-canvas { font: 400 13px/20px var(--v2-font); } .v2-root .v2-connect__cursor .v2-connect__link { margin-top: 0; } +.v2-connect__rule { margin-top: 12px; } +.v2-connect__rule .v2-connect__code-head span { min-width: 0; } +.v2-connect__rule .v2-connect__code-head code { + font-family: "SF Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + overflow-wrap: anywhere; +} .v2-connect__actions { display: flex; From c35bc96baa58898a39193d2d99fc691295313e15 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 01:05:56 -0700 Subject: [PATCH 2/2] test(connect): pin the Cursor rule's instructions and its card's wrap rules Answers two CodeRabbit threads on #2670. One test asserts each instruction the rule must keep: context first, the handoff, and agent-key safety. The other is a layout presence test for the rule card's min-width and overflow-wrap declarations. Mutation check: dropping the overflow-wrap declaration or the key sentence fails 4 tests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ENddpGzhE7S8qjDSYAMuu --- frontend/src/v2/__tests__/cursorProjectRule.test.ts | 6 ++++++ frontend/src/v2/__tests__/v2-layout-invariants.test.ts | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/frontend/src/v2/__tests__/cursorProjectRule.test.ts b/frontend/src/v2/__tests__/cursorProjectRule.test.ts index 5e700c2e1e..4faa66de0b 100644 --- a/frontend/src/v2/__tests__/cursorProjectRule.test.ts +++ b/frontend/src/v2/__tests__/cursorProjectRule.test.ts @@ -28,6 +28,12 @@ describe('Cursor project rule', () => { named.forEach((tool) => expect(tools).toContain(`name: '${tool}'`)); }); + test('the rule keeps its context, handoff and key-safety instructions', () => { + expect(CURSOR_PROJECT_RULE).toMatch(/call `commonly_get_context` with that pod's id before you start/); + expect(CURSOR_PROJECT_RULE).toMatch(/post a short result or handoff[^.]*`commonly_post_message`/); + expect(CURSOR_PROJECT_RULE).toMatch(/Keep agent keys out of pod messages, URLs, logs and committed files/); + }); + test('the rule does not claim Cursor answers @mentions or point at this repo', () => { expect(CURSOR_PROJECT_RULE).toMatch(/does not wake Cursor for @mentions/); expect(CURSOR_PROJECT_RULE).not.toMatch(/AGENTS\.md|REVIEW\.md/); diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 134fcf3ea2..975c0bbe0a 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -268,6 +268,13 @@ describe('v2 layout invariants (CSS rule presence)', () => { expect(ruleBody(v2, '.v2-root .v2-byo__needs')).toContain('color: var(--v2-text-primary)'); }); + // #2670: the Cursor rule card's header names an unbroken save path; without + // these two declarations it pushes the Copy button out of the card at 390px. + test('Cursor rule card header wraps its save path inside the card', () => { + expect(ruleBody(v2, '.v2-connect__rule .v2-connect__code-head span')).toContain('min-width: 0'); + expect(ruleBody(v2, '.v2-connect__rule .v2-connect__code-head code')).toContain('overflow-wrap: anywhere'); + }); + test('Your Team card name owns its line so the category chip cannot crush it', () => { // Direction C: the name sits in its own column of the card head; the // crush guard is min-width 0 on the name and the head, not a flex basis.