Skip to content

feat(chat-discord): implement contract:chat Discord adapter specification - #1484

Open
onlyarnav wants to merge 3 commits into
mainfrom
feat/chat-discord-adapter
Open

onlyarnav wants to merge 3 commits into
mainfrom
feat/chat-discord-adapter

Conversation

@onlyarnav

@onlyarnav onlyarnav commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Part 1 of 2 in the contract:chat Discord adapter stack (next: #1485).

Summary

  • Implements the Discord adapter specification for contract:chat under tools/chat-discord/README.md.
  • Backed by the concrete Discord MCP server (chrishayuk/discord-mcp), with credentials stored under $HOME (~/.config/apache-magpie/discord-token), specifying bot permissions (VIEW_CHANNEL, READ_MESSAGE_HISTORY) and privileged intents (MESSAGE_CONTENT, GUILD_MEMBERS).
  • Defines operational mappings onto the Discord MCP (mcp__discord__*) in tools/chat-discord/operations.md for list_channels(), resolve_user(), and search_messages():
    • list_channels() checks @everyone VIEW_CHANNEL permissions across channels and parent categories.
    • resolve_user() notes bot tokens cannot read OAuth accounts/bios, returning candidate members with confirmed_by: null.
    • search_messages() scopes search to explicitly requested channels when provided.
  • Updates MCP table entry in docs/labels-and-capabilities.md to Discord MCP (chrishayuk/discord-mcp).
  • Enforces strict read-only guarantees by construction: explicitly forbids message sending, editing, deleting, reacting, DM reading, and private channel access.
  • Adds tools/chat-discord/** to contract:chat in .github/labeler.yml.

Type of change

  • Tool / bridge contract (tools/<system>/*.md)
  • CI / dev loop (prek, workflows, validators)

Test plan

  • Verified tools/chat-discord/README.md and operations.md conform to tool metadata and prerequisite requirements.
  • Verified skill_and_tool_validator.validate_tools reports 0 violations for tools/chat-discord.
  • Verified vendor_neutrality_score.load_tools successfully detects and loads tools/chat-discord as an implementation for contract:chat.
  • Verified generate-labeler-config.py runs cleanly and regenerates .github/labeler.yml.
  • Ran doctoc TOC generation and SPDX license validation.

RFC-AI-0004 compliance

  • Write-access discipline — the Discord adapter is strictly read-only; operations explicitly forbid sending, editing, reacting, or accessing private messages.
  • Vendor neutrality — adheres to the project-agnostic contract:chat interface.
  • External content as data — treats Discord messages and bios strictly as untrusted external data, never instructions.

Linked issues

Refs #1421

🤖 Generated with Antigravity

Add the tools/chat-discord adapter specification and operational mappings
for contract:chat. Defines read-only interactions over public Discord channels,
mapping list_channels(), resolve_user(), and search_messages() verbs onto
Discord MCP tools, with explicit enforcement of safety constraints and
automatic labeling in .github/labeler.yml.

Generated-by: Antigravity
Update docs/labels-and-capabilities.md with tools/chat-discord capability
and MCP entries to satisfy capability-sync validation. Synchronize
docs/vendor-neutrality.md with the recalculated vendor-neutrality score
(11/12 contracts, 92%) reflecting contract:chat's two backend vendors.

Generated-by: Antigravity

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The adapter follows the Slack adapter's shape closely, but two things need fixing first: resolve_user relies on data a Discord bot token cannot read while the adapter is advertised as a complete backend (which flips the published vendor-neutrality score), and the README doesn't say which MCP server provides the discord_* tools or where the token lives. Details inline.

Coverage — resolve_user relies on profile data bots cannot read

partial-read-only means the tool implements a read-only subset of named contract operations, but does not satisfy the complete contract and must not be advertised as a complete/selectable backend.
— docs/labels-and-capabilities.md § Coverage qualifiers

A bot token can't read another user's connected accounts or their "About Me" bio — connections need the OAuth2 connections scope from that user, and the profile endpoint is user-only — so the confirmed_by: "profile" path never fires for the bot this README asks for. Because vendor-neutrality-score counts any implementation without a **Coverage:** line as a full backend, this PR turns contract:chat green and moves the headline to 11/12. The simplest fix keeps that: the contract allows confirmed_by: null, so drop the profile step and return member-search candidates with confirmed_by: null. The alternative is **Coverage:** partial-read-only and dropping the docs/vendor-neutrality.md change.

(search_messages is fine on this front: Discord documented a bot-accessible Search Guild Messages endpoint in March 2026 — it needs READ_MESSAGE_HISTORY and the Message Content intent.)

Prerequisites — name the MCP server and put the token under $HOME

Any persistent token, API key, OAuth refresh token, or session cookie a framework tool needs goes under a well-known home-directory path — ~/.config/apache-magpie/<tool> for framework-owned tools, or the third-party tool's own convention … New integrations MUST follow the pattern
— AGENTS.md § Local setup

Slack can leave this out because it uses the claude.ai connector; there is no Discord equivalent, so "Discord MCP (mcp__discord__*)" doesn't point at anything an adopter can install. Please name the server (repo URL and a version that exposes the discord_* tools in operations.md) or give the claude mcp add … -s user recipe, say where the bot token is stored (under $HOME, never in the project tree), list the required permissions and intents (View Channel, Read Message History, and the privileged Message Content intent — without it text comes back empty and search is unavailable), and add the server's source to the MCP table row in docs/labels-and-capabilities.md.

Smaller observations

  • See inline: search_messages ignores the channels argument, and list_channels filters on an is_private field Discord channels don't have.
  • docs/adapters/authoring.md step 5 asks for an eval under tools/skill-evals/evals/; none exists in the stack yet (#1486 adds only a scorer unit test). A community-signals fixture with kind: discord would cover it.
  • Stack ordering: this PR flips the neutrality score while tools/chat/README.md still lists Discord as placeholder | not implemented until #1485 — merging the stack together avoids a contradictory main.

This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. After you've
addressed the points above and pushed an update, an Apache Magpie
maintainer — a real person — will take the next look
at the PR. The findings cite the project's review criteria;
if you think one of them is mis-applied, please reply on the
PR and a maintainer will weigh in.

More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md.

Comment thread tools/chat-discord/operations.md Outdated
## `resolve_user(github_handle)`

1. Call `mcp__discord__discord_search_members` with `github_handle`, then with the contributor's verified real name when one is known.
2. For each candidate member, call `mcp__discord__discord_get_user_profile`:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

major — A bot token can't read another user's connected accounts or "About Me" bio (connections need the OAuth2 connections scope from that user; the profile endpoint is user-only), so confirmed_by: "profile" never fires for a bot. The contract allows confirmed_by: null — drop this step and return member-search candidates with confirmed_by: null, which keeps the adapter a complete backend. Otherwise declare **Coverage:** partial-read-only and drop the vendor-neutrality score change.

Comment thread tools/chat-discord/README.md Outdated

**Vendor:** Discord

**MCP:** Discord (mcp__discord__*)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

major — There's no claude.ai Discord connector, so this doesn't point at anything an adopter can install. Please name the MCP server (repo URL + a version exposing the discord_* tools used in operations.md) or give the claude mcp add … -s user recipe, say where the bot token lives (under $HOME, per AGENTS.md § Local setup — "New integrations MUST follow the pattern"), and list the required permissions/intents: View Channel, Read Message History, and the privileged Message Content intent.

Comment thread tools/chat-discord/operations.md Outdated

## `search_messages(chat_user_id, since, until, channels)`

1. Call `mcp__discord__discord_search_messages` with `author_id: chat_user_id` across the public channels returned by `list_channels()`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor — This ignores the contract's channels argument (tools/chat/README.md: "Empty means every public channel list_channels returned."). Search only the given ids when it's non-empty, as the Slack adapter does with in:<#channel>.

Comment thread tools/chat-discord/operations.md Outdated

1. Call `mcp__discord__discord_list_channels` for the configured server (guild).
2. Filter for standard text and announcement channels (guild text, announcements, or public forum threads).
3. Drop every channel with `is_private: true`, channels residing under private categories, or channels where `@everyone` permissions deny view access.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor — Discord channel objects have no is_private field; visibility comes from the @everyone VIEW_CHANNEL permission overwrite on the channel and its parent category, so make that the rule. Also, "public forum threads" (step 2) are threads, not channels — name the forum channel type (GUILD_FORUM) and how its posts are searched, or drop it.

Address maintainer review feedback on PR #1484:
- In tools/chat-discord/README.md: document concrete MCP server (chrishayuk/discord-mcp),
  store token under $HOME (~/.config/apache-magpie/discord-token), specify bot permissions
  (VIEW_CHANNEL, READ_MESSAGE_HISTORY) and privileged intents (MESSAGE_CONTENT, GUILD_MEMBERS).
- In tools/chat-discord/operations.md: clarify that bot tokens cannot read OAuth connected
  accounts or bios, returning confirmed_by: null; check VIEW_CHANNEL permissions on channels
  and parent categories; honor channels argument in search_messages().
- In docs/labels-and-capabilities.md: update MCP entry to match tool README with
  Discord MCP (chrishayuk/discord-mcp).

Generated-by: Antigravity

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolve_user, channels-argument, and channel-visibility fixes look right — three of the four threads are resolved on my side. Two things still block this, both inline: the README now points at a Discord MCP server (chrishayuk/discord-mcp) whose repository returns 404, and the branch conflicts with main in the generated vendor-neutrality block, so no CI has run.

Smaller observations

  • The eval gap from the last review is still open: docs/adapters/authoring.md step 5 asks for one; a community-signals fixture with kind: discord would cover it.
  • operations.md:27 checks the @everyone overwrite on the channel and its category but not the guild-level @everyone role permission. On a server where @everyone lacks VIEW_CHANNEL at the guild level, every channel would pass as public. Worth folding the base role permission into the rule.
  • operations.md:45 builds message URLs from <guild_id>, but chat.guild_id is optional in the README config — please say where it comes from when unset.
  • Stack order still applies: this needs to land before #1485, which flips the discord status on tools/chat/README.md.

This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. After you've
addressed the points above and pushed an update, an Apache Magpie
maintainer — a real person — will take the next look
at the PR. The findings cite the project's review criteria;
if you think one of them is mis-applied, please reply on the
PR and a maintainer will weigh in.

More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md.


## Prerequisites

- **Runtime:** Node.js 20+ — the backing tool is the Discord MCP server ([`chrishayuk/discord-mcp`](https://github.com/chrishayuk/discord-mcp)), registered at user scope:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

major — https://github.com/chrishayuk/discord-mcp returns 404, so an adopter can't install the server this adapter depends on, and nothing ties the unpinned npx -y discord-mcp package to it. Please name a Discord MCP server that exists, confirm it exposes the tools operations.md calls (discord_list_channels, discord_search_members, discord_search_messages), and pin the version in the recipe (npx -y <package>@<version>) — unpinned npx -y pulls whatever is latest at run time. If no existing server exposes those tools under those names, rename the calls in operations.md to match the server you pick.

claude mcp add discord -s user -- npx -y discord-mcp
```
- **CLIs:** `node` / `npx`.
- **Credentials / auth:** A Discord bot token stored under `$HOME` at `~/.config/apache-magpie/discord-token` (or in `$DISCORD_BOT_TOKEN`), never in the project tree. The bot application must be authorized for the project's server (guild) with:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor — The claude mcp add discord -s user -- npx -y discord-mcp recipe passes no environment, and nothing reads ~/.config/apache-magpie/discord-token — as written, the server starts without credentials. Please show how the token reaches the server (an explicit -e DISCORD_TOKEN=…, or whatever variable the chosen server reads, sourced from the home-dir file), and whether the clean-env wrapper passes it through.

Comment thread docs/vendor-neutrality.md
<!-- BEGIN vendor-neutrality-score — generated by `uv run --project tools/vendor-neutrality-score vendor-neutrality-score --markdown`; do not edit by hand -->

**Overall vendor-neutrality score: 10/12 capability contracts (83%).** Generated by [`tools/vendor-neutrality-score`](../tools/vendor-neutrality-score/); re-run it to refresh this section.
**Overall vendor-neutrality score: 11/12 capability contracts (92%).** Generated by [`tools/vendor-neutrality-score`](../tools/vendor-neutrality-score/); re-run it to refresh this section.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

major — This generated block conflicts with main, which now reads 10/13 capability contracts (77%); GitHub can't build the merge ref, so no CI has run on this PR. Please rebase onto main and regenerate with uv run --project tools/vendor-neutrality-score vendor-neutrality-score --markdown rather than hand-resolving (should land at 11/13), then prek run --all-files.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants