Skip to content

Let a connected app anchor a chat post to a quoted sentence #351

Description

@HMarzban

Parent

#328. Related: #228

What to build

A connected app can post in a heading chat, but its post cannot point at a sentence. A Comment made in docs.plus carries a quote, and its jump button scrolls the pad to that quote. This issue adds an optional quote to post_chat_message, so the post becomes a Comment on that sentence. An owner can then ask their AI app to leave questions beside the text they are about.

Build it only after the maintainer opens the usage gate and rules whether the MCP server may create a heading chat.

Acceptance criteria

  • The maintainer has posted on this issue that the usage gate is open.
  • The maintainer has posted the heading-chat ruling on this issue: may the MCP server create a heading chat, yes or no.
  • post_chat_message with a valid quote stores a Comment. In the pad, its jump button scrolls to the quoted sentence, on desktop and phone.
  • A post without quote behaves exactly as today.
  • Each of these quotes is refused, with a next step:
    • a quote the pad cannot find;
    • a quote that appears more than once in the pad;
    • a quote outside the section of section_id;
    • a quote over the length cap.
  • The quote is stored exactly as given. @, < and > are removed from text only.
  • The tool reply names the heading title and the quote.
  • The tool description says that heading chat in a public document is public.
  • docs/mcp/reference.md and docs/mcp/README.md describe quote, its length cap, and the heading-chat rule from the ruling.

Blocked by

Agent brief

Type: HITL — the maintainer posts two answers on this issue. (1) Gate: post_chat_message:ok reaches 20 posts a week for four weeks on the admin dashboard page /mcp, then one owner asks for this. (2) The heading-chat ruling: may the MCP server create a heading chat? The maintainer may change the gate numbers.

Category: enhancement

Current behavior:

  • post_chat_message refuses a heading with no chat yet (openChatRoom in apps/hocuspocus.server/src/modules/mcp/tools/chatTools.ts:59-67, text in noRoomText at :29-30). A heading chat starts only when a signed-in person opens it in docs.plus (syncChannel in apps/webapp/src/components/chatroom/hooks/useChannelMetadata.ts).
  • The insert sets no type and no metadata (postMessage in apps/hocuspocus.server/src/modules/mcp/infra/chatStore.ts:100-115; its input type is ChatStore in apps/hocuspocus.server/src/modules/mcp/types.ts).
  • The webapp stores a Comment as type: 'comment' with metadata: { comment } (apps/webapp/src/api/messages/sendCommentMessage.ts:20-30). A text anchor is { v: 1, heading_id, section_title?, kind: 'text', content } (TextCommentAnchor in apps/webapp/src/types/comment.ts).
  • The pad finds a text anchor with focusTextAnchor (apps/webapp/src/utils/scrollToCommentAnchor.ts) and takes the first match in the whole pad. After Jump a Comment to its text when the quoted text crosses bold or a link #341, it matches inside one textblock, across marks.
  • toChatPost removes @, < and > (apps/hocuspocus.server/src/modules/mcp/domain/toChatPost.ts:7).
  • The unread count skips the sender (increment_unread_count_on_new_message in packages/supabase/scripts/10-func-notifications.sql). The owner who asked the agent gets no badge for its posts.

Desired behavior:

  • Add an optional plain-text quote to the post_chat_message input. Cap it at 500 characters, as a named constant beside MAX_CHAT_POST_CHARS. State the cap in docs/mcp/reference.md.
  • Check the quote against the live document JSON (loadContent) with the pad's own unit. The quote sits inside one textblock, appears once in the whole pad, and holds no line break. That textblock lies inside the section of section_id (the findSection range). Refuse otherwise with: "quote: copy plain text from one paragraph under this heading, with no Markdown, that appears once in the document."
  • Put the check in one pure function under apps/hocuspocus.server/src/modules/mcp/domain/.
  • Build the textblock text the way Jump a Comment to its text when the quoted text crosses bold or a link #341 does, so the server check and the pad jump agree.
  • Store type: 'comment' and metadata.comment with v: 1, kind: 'text', heading_id set to the section_id, section_title set to the heading text, and content set to the quote. Keep the via key that Mark MCP chat posts with a fixed connected-app metadata key #336 adds (it lands before Show a "via connected app" label on chat posts from a connected app #344).
  • Heading chat creation follows the heading-chat ruling. On "no": keep the refusal. On "yes": create it with the same row shape as syncChannel, and say so in the docs.

Where to start: post_chat_message and openChatRoom in apps/hocuspocus.server/src/modules/mcp/tools/chatTools.ts; postMessage in apps/hocuspocus.server/src/modules/mcp/infra/chatStore.ts; ChatStore in apps/hocuspocus.server/src/modules/mcp/types.ts; findSection in apps/hocuspocus.server/src/modules/document-content/domain/sections.ts; focusTextAnchor in apps/webapp/src/utils/scrollToCommentAnchor.ts. Line numbers are hints as of 2026-09-28; the agent searches by symbol.

Rules that apply:

  • apps/hocuspocus.server/CLAUDE.md §MCP Connector (owner-only posts, the workspace_id filter on every chat query, the caller as user_id).
  • apps/webapp/CLAUDE.md §Document Comments.
  • AGENTS.md §Test Policy.
  • Doc prose follows .cursor/skills/tech-writer/SKILL.md §Simplified English (house standard).

Verify:

  • The quote check is parsing logic with several branches, so AGENTS.md §Test Policy case (c) allows one unit test, in apps/hocuspocus.server/src/modules/mcp/__tests__/unit/. Cover: a quote inside one run, a quote across a bold word (accepted), a repeated quote, a quote under another heading, and a line break. Prove it by sabotage: make the check always pass, and the test must fail.
  • cd apps/hocuspocus.server && bun test src/modules/mcp && bun run typecheck, then bun run lint from the repo root.
  • Local end to end: run make dev-local, then claude mcp add --transport http docs-plus-local http://localhost:4000/api/mcp, and sign in when the browser opens. In a pad you own, open one heading's chat once. Ask Claude to post a question about one sentence under that heading, with a quote. In the pad, open the chat and press the Comment's jump button. The pad scrolls to the sentence. Check desktop and phone (mobile user agent), light and dark.

Out of scope

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ChatRelated to chat featuresIdea

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions