Skip to content

Add the chat tools to the MCP server #228

Description

@HMarzban

Problem

Three document read tools make a document reader, and nobody adds a connector for that. The chat room per heading is the one docs.plus feature no competitor has, and the connector cannot reach it.

There is no chat route in apps/hocuspocus.server. The OpenAPI spec carries 49 paths and zero chat paths.

The address already exists, so no schema change is needed. channel_id is the heading toc-id (apps/webapp/src/services/openHeadingChatroom.ts:142), and channels.workspace_id is the documentId. So one messages row names both a document and a section.

What to do

Add three chat tools to the MCP server:

  • list_chat_rooms — the rooms in a document.
  • read_chat_thread — the messages under one heading.
  • post_chat_message — a reply in one room, posted as the signed-in person.

Each reads from an existing Supabase table or RPC, but none of them may call Supabase first. A chat room carries no document-privacy rule, so each tool must resolve the slug to its document row and run resolvePrivateAccess in the application tier BEFORE it touches Supabase. That gate is new server work. Treat Row Level Security as the second gate, never the only one.

Acceptance

  • list_chat_rooms returns the rooms of a document the caller can read.
  • read_chat_thread returns messages for one heading, newest last, and truncates at the same limit Mount a stateless MCP server at /api/mcp with the document read tools #226 sets. It says in its result that it truncated, and returns a seq cursor for the next call.
  • post_chat_message posts under the caller's own identity, never a shared one.
  • A private document the caller cannot open returns a refusal, not an empty list.
  • The application-tier document gate runs before any Supabase query, on all three tools, including post_chat_message.
  • read_chat_thread marks its result as untrusted content, because anyone who can open the document can write into the room.

Notes

Chat rooms are created lazily. A heading nobody has opened chat on has no channels row. So a room list is always a subset of the outline, never a superset. A tool that treats them as interchangeable reports rooms as missing when they are merely unborn. Say this in the tool description.

messages already carries a unique client_id index, so a retried post is safe. It also carries a monotonic seq, which is a resume cursor.

Chat content is untrusted text from anyone who can open the document, and it flows into the model. Mark these tools accordingly, and do not let a chat message drive a write in this issue.

Promote the shared gate from denyRead and denyWrite at apps/hocuspocus.server/src/modules/document-conversion/http/controller.ts:47-71, which already run resolvePrivateAccess correctly.

Blocked by #226.

Activity

  1. HMarzban commented on Sep 23, 2026

    @HMarzban
    CollaboratorAuthor

    Built locally on 2026-09-23. It is not deployed yet.

    The MCP server has three chat tools: list_chat_rooms, read_chat_thread and post_chat_message.

    • Each tool resolves the slug and runs the document access rule for the caller before any Supabase query. A private document the caller cannot open returns a refusal, not an empty list.
    • The tools use server credentials. The caller's token is never forwarded.
    • Every query filters on the document and the room id, because channels.id is global.
    • read_chat_thread returns the newest message last. It gives a before_seq cursor for older messages, truncates and says so, and frames chat text as untrusted.
    • post_chat_message posts as the caller, and only in documents the caller owns. It posts only into a room that exists, and it removes @, so a post never sends a notification.
    • Rooms start lazily, and the tool descriptions say so.

    One correction to this issue: the webapp does not write client_id on a message. A retry is safe because of the id primary key, not a client_id index.

  2. HMarzban commented on Sep 29, 2026

    @HMarzban
    CollaboratorAuthor

    Shipped in c68d53947 and deployed on 2026-09-28. list_chat_rooms, read_chat_thread and post_chat_message run the document access rule (openDocument) before any Supabase query. Reads cap at MAX_READ_CHARS, frame chat text as data written by other people, and return a before_seq cursor for older messages. Posts go out as the caller, only in documents the caller owns (maintainer ruling, 2026-09-23), with every @ removed.

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 featuresFeatureenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions