Skip to content

Conversation

@viniciusventura29
Copy link
Contributor

@viniciusventura29 viniciusventura29 commented Nov 17, 2025

  • Introduced a new workspace for the Discord Bot, including essential configurations in package.json and bun.lock.
  • Created the Discord Bot server with tools for managing messages, channels, roles, threads, and webhooks.
  • Added TypeScript configuration, Vite setup, and comprehensive documentation for the Discord Bot features and tools.
  • Implemented a client for interacting with the Discord API, ensuring robust data handling and validation.
  • Included a README.md detailing installation, configuration, and usage instructions for the Discord Bot MCP server.

Summary by CodeRabbit

  • New Features

    • Full Discord Bot MCP server: message, channel, guild, role, thread, webhook, and user tools; unified tools index; runtime entrypoint.
    • Typed Discord client, extensive typed payloads/schemas, and webhook signature verification.
    • CLI for registering slash commands.
  • Documentation

    • Comprehensive README with setup, examples, and deployment steps.
  • Chores

    • Project scaffolding: package config, TypeScript/Vite/Wrangler configs, tsconfig, and .gitignore update.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

Walkthrough

Adds a new discord-bot workspace implementing a Deco/Cloudflare Workers MCP server: project configuration and build files, TypeScript types and Zod schemas for Discord, a typed Discord REST client, signature verification and webhook handling, and multiple MCP tool implementations for messages, channels, guilds, roles, threads, and webhooks.

Changes

Cohort / File(s) Summary
Project configuration
discord-bot/.gitignore, discord-bot/package.json, discord-bot/tsconfig.json, discord-bot/vite.config.ts, discord-bot/wrangler.toml, package.json
New workspace and build config files: .gitignore updated (.dev.vars), project package.json with scripts/deps, TypeScript config, Vite config, Wrangler manifest, and root package.json workspace entry.
Documentation
discord-bot/README.md
New README documenting the Discord MCP server: overview, prerequisites, install/dev/build/deploy commands, project structure, and examples.
Runtime entrypoint
discord-bot/server/main.ts
New Cloudflare Workers entrypoint wiring a Deco runtime; exports StateSchema, Env type, and runtime default export; routes webhook fetches to handler.
Constants & verification
discord-bot/server/lib/constants.ts, discord-bot/server/lib/verification.ts
Adds DISCORD_API_URL and Portuguese DISCORD_ERROR_MESSAGES; new signature verification helpers and header extraction for Discord webhooks (Ed25519/tweetnacl-based).
Webhook handler
discord-bot/server/lib/webhook-handler.ts
New Discord interaction webhook handler: signature validation, interaction parsing, routing for PING, commands, components, autocomplete, modals; supports deferred/asynchronous follow-ups and editing original responses.
Types & schemas
discord-bot/server/lib/types.ts
Large set of new TypeScript interfaces and Zod input/output schemas covering Discord entities and MCP payloads (messages, channels, guilds, roles, threads, webhooks, users, etc.).
Discord HTTP client
discord-bot/server/tools/utils/discord-client.ts
New typed Discord REST client factory createDiscordClient(config) and DiscordClientConfig interface; central request logic, endpoints for messages, reactions, pins, channels, guilds, roles, threads, webhooks, users.
Message tools
discord-bot/server/tools/messages.ts
New message-related private tool factories (send/edit/delete/get messages, reactions, pins) and exported messageTools.
Channel tools
discord-bot/server/tools/channels.ts
New channel tools: create channel and get guild channels; exported channelTools.
Guild tools
discord-bot/server/tools/guilds.ts
New guild tools: list bot guilds, get guild, get members, ban member, get current user, get user; exported guildTools.
Role tools
discord-bot/server/tools/roles.ts
New role tools: create, edit, delete, and list guild roles; exported roleTools.
Thread tools
discord-bot/server/tools/threads.ts
New thread tools: create, join, leave, get active and archived threads; exported threadTools.
Webhook tools
discord-bot/server/tools/webhooks.ts
New webhook tools: create, execute, delete, list webhooks; exported webhookTools.
Tools aggregation & generated types
discord-bot/server/tools/index.ts, discord-bot/shared/deco.gen.ts
New tools index aggregating domain tools into tools array and re-exports; generated deco.gen.ts adds Mcp<T> mapped type, base StateSchema, Env with SELF tool map, and Scopes.
Scripts
discord-bot/scripts/register-commands.ts
New CLI script to register/list/delete Discord slash commands via API.

Sequence Diagram(s)

sequenceDiagram
    participant Client as MCP Client
    participant Runtime as Deco Runtime
    participant Tool as Private Tool
    participant DClient as Discord Client
    participant API as Discord API (api/v10)
    participant Webhook as Webhook Handler

    Client->>Runtime: Invoke MCP tool
    Runtime->>Tool: Execute with input
    Tool->>Tool: Validate input (Zod)
    Tool->>DClient: createDiscordClient(botToken)
    Tool->>DClient: Call method (e.g., sendMessage)
    DClient->>API: HTTP request (auth headers, JSON)
    API-->>DClient: Response
    DClient->>Tool: Return parsed result
    Tool->>Tool: Map & validate output (Zod)
    Tool-->>Runtime: Return result
    Runtime-->>Client: Response

    note over Webhook, Runtime: Incoming Discord interaction flow
    Webhook->>Webhook: extractSignatureHeaders & verifyDiscordSignature
    Webhook->>Runtime: dispatch to appropriate handler (commands/components)
    Webhook->>API: (async) follow-up via webhook or bot API
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files needing extra attention:
    • server/lib/types.ts — wide surface of interfaces and Zod schemas; verify alignment with Discord API v10 shapes and schema inputs/outputs.
    • server/lib/webhook-handler.ts — signature verification integration, interaction routing, deferred async handling, and follow-up logic.
    • server/tools/utils/discord-client.ts — URL construction, header/auth handling, request/response parsing and error propagation.
    • Tool modules (messages.ts, channels.ts, guilds.ts, roles.ts, threads.ts, webhooks.ts) — consistency of input/output mapping, error wrapping, and correct use of env.DECO_REQUEST_CONTEXT.state.botToken.
    • server/main.ts and shared/deco.gen.ts — runtime/state typing and Env/SELF wiring.

Suggested reviewers

  • viktormarinho

Poem

🐇 I hopped through types with a tiny cheer,
Threads and webhooks now appear,
Messages, roles, channels in a row,
A carrot-coded MCP to grow,
Puff of bytes — a rabbit's ear!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add Discord Bot MCP server and tools' accurately and concisely summarizes the main change: introducing a new Discord Bot MCP server implementation with associated tools.
Docstring Coverage ✅ Passed Docstring coverage is 85.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-discord

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Nov 17, 2025

🚀 Preview Deployments Ready!

Your changes have been deployed to preview environments:

📦 discord-bot

🔗 View Preview

These previews will be automatically updated with new commits to this PR.


Deployed from commit: 477c808

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (17)
discord-bot/server/lib/constants.ts (1)

3-24: Consider i18n support for error messages.

All error messages are hardcoded in Portuguese. While this may be intentional for the target audience, consider:

  • Adding an i18n/localization layer if the bot will serve multiple locales
  • Documenting the locale choice in comments if Portuguese is the permanent default
discord-bot/server/tools/roles.ts (3)

48-55: Use typed interface instead of any for request body.

The body object is typed as any, which bypasses TypeScript's type safety. Consider creating a typed interface for the role creation payload.

Apply this pattern:

-      const body: any = {
+      interface CreateRoleBody {
+        name?: string;
+        permissions?: string;
+        color?: number;
+        hoist: boolean;
+        mentionable: boolean;
+      }
+
+      const body: CreateRoleBody = {
         hoist,
         mentionable,
       };

93-99: Use typed interface instead of any for request body.

Similar to the create tool, the edit body should be typed for better type safety.


175-184: Type the response mapping with explicit interface.

The .map() callback uses any for the role parameter. Consider adding an explicit type or interface for Discord role objects to improve type safety and catch potential field mismatches.

discord-bot/server/tools/channels.ts (2)

50-68: Use typed interface instead of any for request body.

The body object is typed as any. Consider creating a typed interface for the channel creation payload to leverage TypeScript's type checking.

-      const body: any = {
+      interface CreateChannelBody {
+        name: string;
+        type: number;
+        nsfw: boolean;
+        topic?: string;
+        parent_id?: string;
+        position?: number;
+        bitrate?: number;
+        user_limit?: number;
+        rate_limit_per_user?: number;
+      }
+
+      const body: CreateChannelBody = {
         name,
         type,
         nsfw,
       };

104-109: Type the response mapping with explicit interface.

The .map() callback uses any for the channel parameter. Adding explicit typing would catch potential field mismatches at compile time.

discord-bot/server/tools/webhooks.ts (3)

45-51: Use typed interface instead of any for request body.

Consistent with other tools, the webhook body should be typed for better type safety.


104-116: Use typed interface instead of any for request body.

The execute webhook body should use a typed interface to ensure all fields match the Discord API specification.


217-223: Type the response mapping with explicit interface.

The .map() callback uses any for the webhook parameter. Adding explicit typing would improve type safety.

discord-bot/server/tools/guilds.ts (3)

56-62: Type the response mapping with explicit interface.

The .map() callback uses any for the guild parameter. Consider adding explicit typing for better type safety.


132-143: Type the response mapping with explicit interface.

The nested .map() uses any for the member parameter. Adding explicit types for Discord guild member objects would improve type safety and documentation.


172-178: Use typed interface instead of any for request body.

The ban body should use a typed interface to ensure the payload matches Discord's API specification.

discord-bot/server/tools/utils/discord-client.ts (1)

18-62: Consider extracting error handling to reduce duplication.

The error handling logic on lines 47-52 is separate from makeApiRequest, which likely has its own error handling. This creates two different error message formats and duplication. Consider refactoring to unify error handling.

discord-bot/server/tools/messages.ts (2)

70-89: Consider using SendMessageBody type instead of any.

The request body is typed as any, which reduces type safety. The SendMessageBody interface is available in lib/types.ts (lines 549-563) and could be used here to ensure the body conforms to the expected structure.

Apply this diff to improve type safety:

-      const body: any = {
+      const body: Partial<SendMessageBody> = {
         tts,
       };

Also add the import at the top:

 import {
   sendMessageInputSchema,
   sendMessageOutputSchema,
+  type SendMessageBody,
   // ... other imports
 } from "../lib/types.ts";

223-233: Prefer typed response mapping over any.

The message mapping uses any type for the msg parameter. Consider using the DiscordMessage interface from lib/types.ts for better type safety and IDE support.

Apply this diff:

+import type { DiscordMessage } from "../lib/types.ts";

         return {
-          messages: messages.map((msg: any) => ({
+          messages: (messages as DiscordMessage[]).map((msg) => ({
             id: msg.id,

This same pattern applies to lines 372 (users mapping) and 462 (pinned messages mapping).

discord-bot/server/tools/threads.ts (2)

53-64: Consider using CreateThreadBody type instead of any.

Similar to the message tools, the request body uses any type. The CreateThreadBody interface is available in lib/types.ts (lines 585-596) and should be used for type safety.

Apply this diff:

+import type { CreateThreadBody } from "../lib/types.ts";

-      const body: any = {
+      const body: Partial<CreateThreadBody> = {
         name,
       };

158-163: Same type safety concern with response mapping.

Similar to the messages tools, the thread mapping uses any type. Consider using DiscordChannel interface (since threads are a type of channel in Discord's API) for better type safety. This applies to both lines 158 and 201.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08233bb and eae425a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • discord-bot/.gitignore (1 hunks)
  • discord-bot/README.md (1 hunks)
  • discord-bot/package.json (1 hunks)
  • discord-bot/server/lib/constants.ts (1 hunks)
  • discord-bot/server/lib/types.ts (1 hunks)
  • discord-bot/server/main.ts (1 hunks)
  • discord-bot/server/tools/channels.ts (1 hunks)
  • discord-bot/server/tools/guilds.ts (1 hunks)
  • discord-bot/server/tools/index.ts (1 hunks)
  • discord-bot/server/tools/messages.ts (1 hunks)
  • discord-bot/server/tools/roles.ts (1 hunks)
  • discord-bot/server/tools/threads.ts (1 hunks)
  • discord-bot/server/tools/utils/discord-client.ts (1 hunks)
  • discord-bot/server/tools/webhooks.ts (1 hunks)
  • discord-bot/shared/deco.gen.ts (1 hunks)
  • discord-bot/tsconfig.json (1 hunks)
  • discord-bot/vite.config.ts (1 hunks)
  • discord-bot/wrangler.toml (1 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
discord-bot/server/tools/webhooks.ts (3)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/lib/types.ts (8)
  • createWebhookInputSchema (1098-1102)
  • createWebhookOutputSchema (1104-1110)
  • executeWebhookInputSchema (1112-1132)
  • executeWebhookOutputSchema (1134-1134)
  • deleteWebhookInputSchema (1136-1139)
  • deleteWebhookOutputSchema (1141-1144)
  • listWebhooksInputSchema (1146-1155)
  • listWebhooksOutputSchema (1157-1159)
discord-bot/server/tools/utils/discord-client.ts (1)
  • createDiscordClient (67-257)
discord-bot/server/tools/guilds.ts (4)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/lib/types.ts (12)
  • listBotGuildsInputSchema (855-874)
  • listBotGuildsOutputSchema (876-886)
  • getGuildInputSchema (888-894)
  • getGuildOutputSchema (896-903)
  • getGuildMembersInputSchema (905-917)
  • getGuildMembersOutputSchema (919-934)
  • banMemberInputSchema (936-946)
  • banMemberOutputSchema (948-951)
  • getCurrentUserInputSchema (953-953)
  • getCurrentUserOutputSchema (955-960)
  • getUserInputSchema (962-964)
  • getUserOutputSchema (966-966)
discord-bot/server/tools/utils/discord-client.ts (1)
  • createDiscordClient (67-257)
discord-bot/server/tools/index.ts (1)
  • guildTools (43-43)
discord-bot/server/tools/roles.ts (4)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/lib/types.ts (8)
  • createRoleInputSchema (972-982)
  • createRoleOutputSchema (984-993)
  • editRoleInputSchema (995-1003)
  • editRoleOutputSchema (1005-1005)
  • deleteRoleInputSchema (1007-1011)
  • deleteRoleOutputSchema (1013-1016)
  • getGuildRolesInputSchema (1018-1020)
  • getGuildRolesOutputSchema (1022-1024)
discord-bot/server/tools/utils/discord-client.ts (1)
  • createDiscordClient (67-257)
discord-bot/server/tools/index.ts (1)
  • roleTools (44-44)
discord-bot/server/tools/threads.ts (4)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/lib/types.ts (10)
  • createThreadInputSchema (1030-1046)
  • createThreadOutputSchema (1048-1053)
  • joinThreadInputSchema (1055-1057)
  • joinThreadOutputSchema (1059-1062)
  • leaveThreadInputSchema (1064-1064)
  • leaveThreadOutputSchema (1065-1065)
  • getActiveThreadsInputSchema (1067-1069)
  • getActiveThreadsOutputSchema (1071-1073)
  • getArchivedThreadsInputSchema (1075-1090)
  • getArchivedThreadsOutputSchema (1092-1092)
discord-bot/server/tools/utils/discord-client.ts (1)
  • createDiscordClient (67-257)
discord-bot/server/tools/index.ts (1)
  • threadTools (45-45)
discord-bot/server/tools/channels.ts (4)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/lib/types.ts (4)
  • createChannelInputSchema (814-834)
  • createChannelOutputSchema (836-841)
  • getGuildChannelsInputSchema (843-845)
  • getGuildChannelsOutputSchema (847-849)
discord-bot/server/tools/utils/discord-client.ts (1)
  • createDiscordClient (67-257)
discord-bot/server/tools/index.ts (1)
  • channelTools (42-42)
discord-bot/server/tools/index.ts (6)
discord-bot/server/tools/messages.ts (1)
  • messageTools (484-496)
discord-bot/server/tools/channels.ts (1)
  • channelTools (121-124)
discord-bot/server/tools/guilds.ts (1)
  • guildTools (257-264)
discord-bot/server/tools/roles.ts (1)
  • roleTools (196-201)
discord-bot/server/tools/threads.ts (1)
  • threadTools (218-224)
discord-bot/server/tools/webhooks.ts (1)
  • webhookTools (235-240)
discord-bot/shared/deco.gen.ts (1)
discord-bot/server/main.ts (2)
  • StateSchema (18-24)
  • Env (33-38)
discord-bot/server/main.ts (1)
discord-bot/shared/deco.gen.ts (2)
  • StateSchema (21-21)
  • Env (23-26)
discord-bot/server/tools/utils/discord-client.ts (1)
shared/tools/utils/api-client.ts (1)
  • makeApiRequest (27-39)
discord-bot/server/tools/messages.ts (3)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/tools/utils/discord-client.ts (1)
  • createDiscordClient (67-257)
discord-bot/server/tools/index.ts (1)
  • messageTools (41-41)
discord-bot/vite.config.ts (1)
shared/deco-vite-plugin.ts (1)
  • deco (89-146)
🪛 LanguageTool
discord-bot/README.md

[locale-violation] ~3-~3: “server” é um estrangeirismo. É preferível dizer “servidor”.
Context: ...d Bot MCP MCP (Model Context Protocol) server para integração com Discord Bot API. #...

(PT_BARBARISMS_REPLACE_SERVER)


[typographical] ~53-~53: Símbolo sem par: “]” aparentemente está ausente
Context: ... ### Pré-requisitos 1. Criar um bot no [Discord Developer Portal](https://discor...

(UNPAIRED_BRACKETS)


[uncategorized] ~60-~60: Esta locução deve ser separada por vírgulas.
Context: ...o seu workspace Deco 2. Configure o Bot Token quando solicitado ### Permissões Neces...

(VERB_COMMA_CONJUNCTION)

🪛 markdownlint-cli2 (0.18.1)
discord-bot/README.md

93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (16)
discord-bot/.gitignore (1)

1-1: LGTM!

Correctly ignores .dev.vars, which is the standard file for local environment variables in Cloudflare Workers projects.

package.json (1)

23-23: LGTM!

The new workspace entry is correctly added and maintains the alphabetical ordering of the workspaces array.

discord-bot/shared/deco.gen.ts (1)

1-28: LGTM!

This is a generated types file that provides the foundational type infrastructure for the MCP tooling. The Mcp<T> type correctly wraps tool functions with schema validation and metadata, and the base StateSchema, Env, and Scopes exports align with the extended definitions in server/main.ts.

discord-bot/vite.config.ts (1)

1-29: LGTM!

The Vite configuration correctly sets up the Cloudflare Workers environment with the Deco plugin. The environment variable definitions and global shims are appropriate for the Cloudflare Workers runtime, and the cache directory configuration is a reasonable optimization.

discord-bot/tsconfig.json (1)

1-43: LGTM!

The TypeScript configuration is well-structured with appropriate strict type-checking rules, correct module resolution for bundlers, and path aliases that align with the project structure. The Cloudflare Workers types are correctly included.

discord-bot/server/main.ts (1)

1-62: LGTM! Clean entry point configuration.

The main entry point is well-structured with proper StateSchema extension for botToken configuration, comprehensive Env type definition, and appropriate runtime wiring. The empty OAuth scopes array is correct for a bot token-based authentication model.

discord-bot/server/tools/index.ts (1)

1-46: LGTM! Well-organized tool aggregation.

The central export pattern maintains clear domain separation while providing a convenient single entry point. The dual export strategy (aggregated + domain-specific) offers flexibility for different consumption patterns.

discord-bot/server/tools/utils/discord-client.ts (1)

44-59: Verification confirms code is correct.

All PUT endpoints in the codebase—pinMessage (204 No Content), banMember (204 No Content), and joinThread (204 No Content)—return void on success. The special handling at lines 45-59 correctly checks the content-type header and returns parsed JSON only if present, otherwise returns void. This aligns with Discord API specifications and is an intentional, appropriate design choice.

discord-bot/server/tools/webhooks.ts (1)

126-139: Confirm that empty ID values in 204 No Content response are acceptable for your use cases.

Verification confirms the fallback object with empty string IDs (id="", channel_id="", author.id="") is returned to consumers when wait=false (204 No Content). The schema allows empty strings since sendMessageOutputSchema uses z.string() without constraints, so validation passes. However, ensure downstream consumers can handle these empty placeholder values without breaking or logging errors. If consumers require valid IDs, consider either not returning this fallback or documenting the limitation.

discord-bot/server/tools/messages.ts (1)

209-211: LGTM! Good defensive limit clamping.

The use of Math.min(Math.max(limit, 1), 100) correctly ensures the limit stays within Discord API constraints (1-100 for messages). This pattern is consistently applied across pagination endpoints.

discord-bot/server/tools/threads.ts (1)

47-49: Thread name validation is correct.

The validation properly enforces Discord's 1-100 character limit for thread names. Note that the !name check is technically redundant since the input schema already validates name as a required string, but it provides good defensive programming.

discord-bot/server/lib/types.ts (5)

619-657: LGTM! Comprehensive embed schema.

The discordEmbedSchema correctly models Discord's embed structure with all supported fields (title, description, color, footer, image, thumbnail, author, fields). The nested object schemas properly validate the embed components.


659-678: Schema descriptions in Portuguese are intentional.

The Zod schema descriptions are written in Portuguese (e.g., "ID do canal Discord onde enviar a mensagem"). This appears to be an intentional internationalization choice and is consistently applied throughout the file. Ensure this aligns with your target audience expectations.


760-761: Good schema reuse pattern.

Reusing schemas where input/output shapes are identical (e.g., removeReactionInputSchema = addReactionInputSchema) is a good application of the DRY principle. This pattern is consistently applied throughout the file (also at lines 799-800, 1064-1065).


609-612: Well-organized type definition file.

The clear separation between TypeScript interfaces (lines 1-608) and Zod schemas (lines 609-1159) with section markers makes this large file easy to navigate. The consistent naming convention (*InputSchema/*OutputSchema) enhances maintainability.


1-18: No issues found with discriminator field implementation.

The Discord API v10 User object still includes the discriminator field, and it's always returned—migrated users receive discriminator = "0". The type definition correctly marks it as required (discriminator: string;) since the API consistently returns this field. The application can handle the "0" value gracefully at runtime if needed, but the type definition is accurate for the current API specification.

"dev": "deco dev --vite",
"configure": "deco configure",
"gen": "deco gen --output=shared/deco.gen.ts",
"deploy": "npm run build && deco deploy ./dist/server",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the package manager inconsistency in the deploy script.

The deploy script uses npm run build, but the project uses Bun as its package manager (evident from the build script and bun.lock mentioned in the PR summary).

Apply this diff to fix the inconsistency:

-    "deploy": "npm run build && deco deploy ./dist/server",
+    "deploy": "bun run build && deco deploy ./dist/server",
🤖 Prompt for AI Agents
In discord-bot/package.json around line 11, the deploy script calls "npm run
build" while the project uses Bun; update the deploy script to invoke Bun
instead (for example replace the npm invocation with "bun run build" so the
script becomes bun run build && deco deploy ./dist/server) to ensure the package
manager is consistent.

"@types/mime-db": "^1.43.6",
"deco-cli": "^0.26.0",
"typescript": "^5.7.2",
"vite": "7.2.0",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Verify the Vite version exists and is compatible.

Vite is pinned to exact version 7.2.0, which seems unusually high. As of my knowledge cutoff, Vite's latest stable version is in the 5.x-6.x range. Please verify that this version exists and is the intended version.


🌐 Web query:

What is the latest version of Vite as of November 2025?

💡 Result:

The latest stable Vite version as of November 17, 2025 is v7.1.4. [1][2]

Sources:
[1] Vite official blog (announcing Vite 7).
[2] Vite package page on npm showing v7.1.4.


Vite version 7.2.0 exceeds the current latest stable release.

The latest stable Vite version as of November 17, 2025 is v7.1.4, but the package.json pins version 7.2.0. This version either doesn't exist yet or may be a pre-release. Verify this is intentional; otherwise, update to a released version or remove the exact pin to allow flexibility.

🤖 Prompt for AI Agents
In discord-bot/package.json around line 28, the Vite dependency is pinned to
"7.2.0" which exceeds the current stable release; update the version to a valid
released version (e.g., "7.1.4") or loosen the pin (e.g., "^7.1.4" or ">=7.1.4
<8.0.0") depending on whether you want exact reproducibility or minor upgrades,
then run npm install / pnpm install and verify the lockfile and build still
succeed.

Comment on lines +93 to +111
```
discord-bot/
├── server/
│ ├── main.ts # Entry point do MCP
│ ├── lib/
│ │ └── types.ts # Schemas Zod e tipos
│ └── tools/
│ ├── index.ts # Exporta todas as tools
│ ├── messages.ts # Tools de mensagens
│ ├── channels.ts # Tools de canais
│ ├── guilds.ts # Tools de servidores
│ ├── roles.ts # Tools de roles
│ ├── threads.ts # Tools de threads
│ ├── webhooks.ts # Tools de webhooks
│ └── utils/
│ └── discord-client.ts # Cliente HTTP Discord
└── shared/
└── deco.gen.ts # Tipos gerados automaticamente
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specification to the fenced code block.

The fenced code block on line 93 is missing a language specification, which is flagged by markdownlint. Adding a language identifier improves syntax highlighting and documentation clarity.

Apply this diff to add the language specification:

-```
+```text
 discord-bot/
 ├── server/
 │   ├── main.ts              # Entry point do MCP
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In discord-bot/README.md around lines 93 to 111 the fenced code block lacks a
language specifier; update the opening fence from ``` to ```text so the block
starts with ```text to satisfy markdownlint and enable proper syntax
highlighting and documentation clarity.

Comment on lines +1075 to +1090
export const getArchivedThreadsInputSchema = z.object({
channelId: z.string().describe("ID do canal Discord"),
type: z.enum(["public", "private"]).describe("Tipo de threads arquivadas"),
before: z
.string()
.optional()
.describe(
"Timestamp ISO8601 - retornar threads arquivadas antes desta data",
),
limit: z
.number()
.min(1)
.max(100)
.optional()
.describe("Número máximo de threads a retornar"),
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify before parameter type for archived threads.

The before parameter (lines 1078-1083) is typed as a string with description "Timestamp ISO8601". However, Discord's API typically uses snowflake IDs (message/thread IDs) for pagination parameters, not ISO8601 timestamps. Verify that the Discord API documentation for archived threads actually accepts timestamps rather than snowflake IDs.


🌐 Web query:

Discord API v10 get archived threads before parameter type

💡 Result:

The before query accepts either an ISO8601 timestamp (string) or a Snowflake (ID). [1][2]

Sources:

  • Discord docs (List Public/Private Archived Threads: before = ISO8601 timestamp). [1]
  • discord-api-types v10 typings: before?: Snowflake | string. [2]

Update before parameter type to accept both ISO8601 timestamps and Snowflake IDs.

The before parameter (lines 1078-1083) is currently typed as string only. Discord API v10's before query accepts either an ISO8601 timestamp (string) or a Snowflake (ID). The schema should be updated to accept both types, and the description should clarify both accepted formats.

before: z
  .union([z.string(), z.number()])
  .optional()
  .describe(
    "ISO8601 timestamp ou Snowflake ID - retornar threads arquivadas antes desta data/ID",
  ),
🤖 Prompt for AI Agents
In discord-bot/server/lib/types.ts around lines 1075 to 1090, the
getArchivedThreadsInputSchema's before field is currently only z.string();
change it to accept both ISO8601 timestamps and Snowflake IDs by using a union
of string and number (e.g., z.union([z.string(), z.number()]).optional()) and
update the .describe() text to mention both accepted formats in Portuguese
("ISO8601 timestamp ou Snowflake ID - retornar threads arquivadas antes desta
data/ID"), keeping the field optional and preserving existing validation for
other fields.

botToken: string;
}

const DISCORD_API_URL = "https://discord.com/api/v10";
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove duplicate DISCORD_API_URL constant.

This constant is already defined in discord-bot/server/lib/constants.ts. Duplicating it creates a maintenance burden and potential inconsistency.

Apply this diff:

+import { DISCORD_API_URL } from "../../lib/constants.ts";
+
 export interface DiscordClientConfig {
   botToken: string;
 }

-const DISCORD_API_URL = "https://discord.com/api/v10";
-
 /**
  * Makes an authenticated request to the Discord API
  */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const DISCORD_API_URL = "https://discord.com/api/v10";
import { DISCORD_API_URL } from "../../lib/constants.ts";
export interface DiscordClientConfig {
botToken: string;
}
/**
* Makes an authenticated request to the Discord API
*/
🤖 Prompt for AI Agents
In discord-bot/server/tools/utils/discord-client.ts around line 13, remove the
duplicated DISCORD_API_URL constant and instead import DISCORD_API_URL from
discord-bot/server/lib/constants.ts; update the top-of-file imports to include
that named import (use the correct relative path), delete the local const
declaration, and ensure all usages in this file reference the imported constant.

#:schema node_modules/@decocms/runtime/config-schema.json
name = "discord-bot"
main = "server/main.ts"
compatibility_date = "2025-06-17"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the future compatibility date.

The compatibility_date is set to "2025-06-17", which is in the past relative to today (November 17, 2025). Cloudflare Workers compatibility dates should reflect the API behavior you want to lock in, but shouldn't be set to dates before the code was written. Consider using a date closer to the initial development date or the current date.

Apply this diff to use the current date:

-compatibility_date = "2025-06-17"
+compatibility_date = "2025-11-17"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
compatibility_date = "2025-06-17"
compatibility_date = "2025-11-17"
🤖 Prompt for AI Agents
In discord-bot/wrangler.toml around line 4, the compatibility_date is set to
"2025-06-17" which is in the past; update the compatibility_date value to the
current development date (e.g., "2025-11-17") so the Workers runtime locks to
the intended API behavior and reflects a date not earlier than when the code was
written.

[deco.integration]
description = "Discord Bot integration for sending messages, managing channels, roles, threads, webhooks, and server moderation. Full control of your Discord server through MCP tools."
icon = "https://support.discord.com/hc/user_images/PRywUXcqg0v5DD6s7C3LyQ.jpeg"
friendlyName = "Discord teste"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update the test-related friendly name for production.

The friendlyName is set to "Discord teste" (Portuguese for "Discord test"), which suggests this is a test configuration. For a production integration, this should have a proper production-ready name.

Apply this diff to use a production-appropriate name:

-friendlyName = "Discord teste"
+friendlyName = "Discord Bot"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
friendlyName = "Discord teste"
friendlyName = "Discord Bot"
🤖 Prompt for AI Agents
In discord-bot/wrangler.toml around line 16, the friendlyName is set to "Discord
teste" which indicates a test label; replace that value with a
production-appropriate name (for example "Discord", "Discord Bot", or "Discord
Integration") so the configuration reflects production usage and commit the
updated toml entry.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
discord-bot/server/tools/guilds.ts (2)

35-37: Redundant limit clamping (consider removing).

The Zod schema listBotGuildsInputSchema already enforces .min(1).max(200) validation on the limit parameter. This manual clamping is redundant since invalid values would be rejected during schema validation before reaching this code.

You may simplify to:

-      const searchParams: Record<string, string> = {
-        limit: Math.min(Math.max(limit, 1), 200).toString(),
-      };
+      const searchParams: Record<string, string> = {
+        limit: limit.toString(),
+      };

Alternatively, if you prefer defensive programming to protect against future schema changes, the current approach is acceptable.


113-115: Redundant limit clamping (consider removing).

Similar to LIST_BOT_GUILDS, the Zod schema getGuildMembersInputSchema already enforces .min(1).max(1000) validation. This manual clamping is redundant.

-      const searchParams: Record<string, string> = {
-        limit: Math.min(Math.max(limit, 1), 1000).toString(),
-      };
+      const searchParams: Record<string, string> = {
+        limit: limit.toString(),
+      };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eae425a and 241f67e.

📒 Files selected for processing (2)
  • discord-bot/server/tools/guilds.ts (1 hunks)
  • discord-bot/shared/deco.gen.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
discord-bot/shared/deco.gen.ts (1)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/tools/guilds.ts (5)
discord-bot/shared/deco.gen.ts (1)
  • Env (1925-2114)
discord-bot/server/main.ts (1)
  • Env (33-38)
discord-bot/server/lib/types.ts (12)
  • listBotGuildsInputSchema (855-874)
  • listBotGuildsOutputSchema (876-886)
  • getGuildInputSchema (888-894)
  • getGuildOutputSchema (896-903)
  • getGuildMembersInputSchema (905-917)
  • getGuildMembersOutputSchema (919-934)
  • banMemberInputSchema (936-946)
  • banMemberOutputSchema (948-951)
  • getCurrentUserInputSchema (953-953)
  • getCurrentUserOutputSchema (955-960)
  • getUserInputSchema (962-964)
  • getUserOutputSchema (966-966)
discord-bot/server/tools/utils/discord-client.ts (1)
  • createDiscordClient (67-257)
discord-bot/server/tools/index.ts (1)
  • guildTools (43-43)
🪛 Biome (2.1.2)
discord-bot/shared/deco.gen.ts

[error] 412-414: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 1050-1054: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 1472-1474: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🔇 Additional comments (4)
discord-bot/server/tools/guilds.ts (1)

22-254: LGTM! Clean implementation of guild tools.

The overall structure is well-organized with:

  • Consistent pattern across all tool factories
  • Proper schema validation using Zod
  • Good error handling and error message wrapping
  • Clear JSDoc comments for each tool
  • Proper mapping from Discord API responses to output schemas
discord-bot/shared/deco.gen.ts (3)

1-8: Generated file properly marked.

The file header correctly indicates this is auto-generated by json-schema-to-typescript and should not be manually edited. This is the expected pattern for generated TypeScript definitions.


412-412: Empty interfaces in generated code (informational).

Static analysis correctly identifies empty interfaces that could be type aliases. However, since this file is auto-generated by json-schema-to-typescript, these patterns are expected and should not be manually changed. If needed, adjust the source JSON Schema or the code generation configuration instead.

Also applies to: 1050-1050, 1470-1470


1905-2116: LGTM! Well-structured MCP type definitions.

The generated MCP wiring provides:

  • Strong TypeScript typing for all Discord operations via the Mcp<T> generic type
  • Comprehensive Env interface mapping all 32+ Discord MCP actions with proper input/output types
  • Clear JSDoc descriptions for each operation
  • Proper integration with Zod for runtime validation

The type structure correctly aligns with the tool implementations in discord-bot/server/tools/guilds.ts and other tool modules.

Comment on lines +156 to +158
if (deleteMessageDays < 0 || deleteMessageDays > 7) {
throw new Error("deleteMessageDays must be between 0 and 7");
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove redundant validation logic.

The Zod schema banMemberInputSchema already validates that deleteMessageDays is between 0 and 7 using .min(0).max(7). This manual validation duplicates that logic and will never be reached with invalid values, since Zod validation occurs before the execute function runs.

-      if (deleteMessageDays < 0 || deleteMessageDays > 7) {
-        throw new Error("deleteMessageDays must be between 0 and 7");
-      }
-
       const client = createDiscordClient({ botToken: state.botToken });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (deleteMessageDays < 0 || deleteMessageDays > 7) {
throw new Error("deleteMessageDays must be between 0 and 7");
}
const client = createDiscordClient({ botToken: state.botToken });
🤖 Prompt for AI Agents
In discord-bot/server/tools/guilds.ts around lines 156 to 158, the manual check
that throws "deleteMessageDays must be between 0 and 7" is redundant because
banMemberInputSchema already enforces .min(0).max(7) and Zod validates inputs
before execute runs; remove the three-line manual validation block and any
related unreachable branches so the function relies on the schema validation
instead.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (3)
discord-bot/package.json (2)

11-11: Fix the package manager inconsistency in the deploy script.

The deploy script uses npm run build, but the project uses Bun as its package manager (evident from the build script on line 13 and the PR summary mentioning bun.lock). This inconsistency was flagged in a previous review.

Apply this diff to fix it:

-    "deploy": "npm run build && deco deploy ./dist/server",
+    "deploy": "bun run build && deco deploy ./dist/server",

29-29: Vite version exceeds the current stable release.

Vite is pinned to exact version 7.2.0, which exceeds the current stable release (7.1.4 as of November 17, 2025, per previous review findings). Verify this version exists and is intentional; otherwise, update to a released version.

Apply this diff to use the latest stable release:

-    "vite": "7.2.0",
+    "vite": "^7.1.4",

Alternatively, if you prefer exact reproducibility, use:

-    "vite": "7.2.0",
+    "vite": "7.1.4",
discord-bot/server/lib/types.ts (1)

1075-1090: Clarify and widen before filter for archived threads.

getArchivedThreadsInputSchema.before is documented only as an ISO8601 timestamp, but Discord also accepts a Snowflake ID for pagination. Consider updating both the type and description so tools can pass either an ISO string or an ID:

-export const getArchivedThreadsInputSchema = z.object({
+export const getArchivedThreadsInputSchema = z.object({
   channelId: z.string().describe("ID do canal Discord"),
   type: z.enum(["public", "private"]).describe("Tipo de threads arquivadas"),
-  before: z
-    .string()
-    .optional()
-    .describe(
-      "Timestamp ISO8601 - retornar threads arquivadas antes desta data",
-    ),
+  before: z
+    .union([z.string(), z.number()])
+    .optional()
+    .describe(
+      "ISO8601 timestamp ou Snowflake ID - retornar threads arquivadas antes desta data/ID",
+    ),
   limit: z
     .number()
     .min(1)
     .max(100)
     .optional()
     .describe("Número máximo de threads a retornar"),
 });

This keeps the field optional while matching Discord’s documented behavior; ensure the HTTP client stringifies numeric values before sending.

🧹 Nitpick comments (3)
discord-bot/server/lib/types.ts (1)

427-432: Avoid duplicate message reference interfaces.

DiscordMessageReference and MessageReference are structurally identical. Maintaining two copies increases drift risk; a single exported type/interface (or a type alias) is enough:

-export interface DiscordMessageReference {
-  message_id?: string;
-  channel_id?: string;
-  guild_id?: string;
-  fail_if_not_exists?: boolean;
-}
-
-export interface MessageReference {
-  message_id?: string;
-  channel_id?: string;
-  guild_id?: string;
-  fail_if_not_exists?: boolean;
-}
+export interface DiscordMessageReference {
+  message_id?: string;
+  channel_id?: string;
+  guild_id?: string;
+  fail_if_not_exists?: boolean;
+}
+
+export type MessageReference = DiscordMessageReference;

This keeps both names available without duplicating the structure.

Also applies to: 542-547

discord-bot/server/lib/verification.ts (1)

31-82: Signature verification logic is solid; async modifier is optional.

The Ed25519 verification flow (timestamp window, timestamp + body message, hex decoding, and nacl.sign.detached.verify) is correct and should interop with Discord’s webhook requirements. Since everything here is synchronous, you could drop the async on verifyDiscordSignature and the corresponding await at call sites to simplify the call graph, but this is purely optional.

discord-bot/server/lib/webhook-handler.ts (1)

251-265: Model autocomplete choices in the interaction data types to avoid @ts-ignore.

handleAutocomplete relies on data.choices, but InteractionCallbackData doesn’t declare this property, forcing an @ts-ignore. To keep type safety, extend your types to cover autocomplete responses instead of bypassing the checker. For example:

-export interface InteractionCallbackData {
+export interface InteractionCallbackData {
   tts?: boolean;
   content?: string;
   embeds?: DiscordEmbed[];
   allowed_mentions?: AllowedMentions;
   flags?: number;
   components?: DiscordMessageComponent[];
   attachments?: DiscordMessageAttachment[];
+  // Para respostas de autocomplete
+  // choices?: { name: string; value: string }[];
 }

Then you can remove the // @ts-ignore in handleAutocomplete.

Also applies to: 1242-1250

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 241f67e and 636ba2f.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • discord-bot/package.json (1 hunks)
  • discord-bot/scripts/register-commands.ts (1 hunks)
  • discord-bot/server/lib/types.ts (1 hunks)
  • discord-bot/server/lib/verification.ts (1 hunks)
  • discord-bot/server/lib/webhook-handler.ts (1 hunks)
  • discord-bot/server/main.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
discord-bot/server/lib/webhook-handler.ts (4)
discord-bot/server/main.ts (1)
  • Env (39-44)
discord-bot/server/lib/verification.ts (2)
  • extractSignatureHeaders (87-94)
  • verifyDiscordSignature (31-82)
discord-bot/server/lib/types.ts (2)
  • DiscordInteraction (1217-1232)
  • InteractionResponse (1237-1240)
discord-bot/server/lib/constants.ts (1)
  • DISCORD_API_URL (1-1)
discord-bot/scripts/register-commands.ts (1)
discord-bot/server/lib/constants.ts (1)
  • DISCORD_API_URL (1-1)
discord-bot/server/main.ts (2)
discord-bot/shared/deco.gen.ts (2)
  • StateSchema (1923-1923)
  • Env (1925-2114)
discord-bot/server/lib/webhook-handler.ts (1)
  • handleDiscordWebhook (21-65)
🪛 GitHub Actions: Checks
discord-bot/server/lib/webhook-handler.ts

[error] 1-1: Formatting issues detected by oxfmt. Run 'bun run fmt' (without --check) to fix this file.

discord-bot/server/lib/verification.ts

[error] 1-1: Formatting issues detected by oxfmt. Run 'bun run fmt' (without --check) to fix this file.

discord-bot/scripts/register-commands.ts

[error] 1-1: Formatting issues detected by oxfmt. Run 'bun run fmt' (without --check) to fix this file.

🔇 Additional comments (1)
discord-bot/server/main.ts (1)

19-80: Runtime and webhook routing look consistent and well-scoped.

StateSchema extension (botToken/discordPublicKey) and the custom fetch handler cleanly integrate Discord webhooks into the Deco runtime while preserving the ASSETS fallback and leaving scopes empty. No issues from a wiring/architecture standpoint.

Comment on lines +1 to +328
/**
* Script para registrar comandos slash no Discord
*
* Uso:
* bun run scripts/register-commands.ts
*
* Requisitos:
* - Defina as variáveis de ambiente:
* - DISCORD_APP_ID: ID da aplicação Discord
* - DISCORD_BOT_TOKEN: Token do bot
* - DISCORD_GUILD_ID (opcional): ID do servidor para comandos de teste
*/

const DISCORD_API_URL = "https://discord.com/api/v10";

// ========================================
// Configuração dos Comandos
// ========================================

const commands = [
{
name: "deco",
description: "Interagir com agente Deco via Discord",
options: [
{
name: "message",
description: "Mensagem para enviar ao agente",
type: 3, // STRING
required: false,
},
],
},
{
name: "ping",
description: "Verificar se o bot está online",
},
{
name: "help",
description: "Mostrar ajuda sobre comandos disponíveis",
},
];

// ========================================
// Funções de Registro
// ========================================

async function registerGlobalCommands(
appId: string,
botToken: string
): Promise<void> {
console.log("📝 Registrando comandos globais...");
console.log(`Total: ${commands.length} comandos`);

const url = `${DISCORD_API_URL}/applications/${appId}/commands`;

try {
const response = await fetch(url, {
method: "PUT",
headers: {
"Authorization": `Bot ${botToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(commands),
});

if (!response.ok) {
const error = await response.text();
throw new Error(
`Falha ao registrar comandos: ${response.status} - ${error}`
);
}

const result = await response.json();
console.log("✅ Comandos globais registrados com sucesso!");
console.log(`Registrados ${result.length} comandos`);

result.forEach((cmd: any) => {
console.log(` - /${cmd.name}: ${cmd.description}`);
});

console.log(
"\n⚠️ Nota: Comandos globais podem demorar até 1 hora para propagar."
);
} catch (error) {
console.error("❌ Erro ao registrar comandos globais:", error);
throw error;
}
}

async function registerGuildCommands(
appId: string,
botToken: string,
guildId: string
): Promise<void> {
console.log(`📝 Registrando comandos no servidor ${guildId}...`);
console.log(`Total: ${commands.length} comandos`);

const url =
`${DISCORD_API_URL}/applications/${appId}/guilds/${guildId}/commands`;

try {
const response = await fetch(url, {
method: "PUT",
headers: {
"Authorization": `Bot ${botToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(commands),
});

if (!response.ok) {
const error = await response.text();
throw new Error(
`Falha ao registrar comandos: ${response.status} - ${error}`
);
}

const result = await response.json();
console.log("✅ Comandos do servidor registrados com sucesso!");
console.log(`Registrados ${result.length} comandos`);

result.forEach((cmd: any) => {
console.log(` - /${cmd.name}: ${cmd.description}`);
});

console.log("\n✅ Comandos devem estar disponíveis imediatamente!");
} catch (error) {
console.error("❌ Erro ao registrar comandos do servidor:", error);
throw error;
}
}

async function listCommands(
appId: string,
botToken: string,
guildId?: string
): Promise<void> {
const scope = guildId ? `servidor ${guildId}` : "globais";
console.log(`📋 Listando comandos ${scope}...`);

const url = guildId
? `${DISCORD_API_URL}/applications/${appId}/guilds/${guildId}/commands`
: `${DISCORD_API_URL}/applications/${appId}/commands`;

try {
const response = await fetch(url, {
headers: {
"Authorization": `Bot ${botToken}`,
},
});

if (!response.ok) {
const error = await response.text();
throw new Error(`Falha ao listar comandos: ${response.status} - ${error}`);
}

const result = await response.json();

if (result.length === 0) {
console.log(`ℹ️ Nenhum comando ${scope} encontrado.`);
return;
}

console.log(`\nComandos ${scope} (${result.length}):`);
result.forEach((cmd: any) => {
console.log(`\n 📌 /${cmd.name}`);
console.log(` ID: ${cmd.id}`);
console.log(` Descrição: ${cmd.description}`);
if (cmd.options && cmd.options.length > 0) {
console.log(` Opções:`);
cmd.options.forEach((opt: any) => {
const required = opt.required ? " (obrigatório)" : "";
console.log(` - ${opt.name}: ${opt.description}${required}`);
});
}
});
} catch (error) {
console.error("❌ Erro ao listar comandos:", error);
throw error;
}
}

async function deleteAllCommands(
appId: string,
botToken: string,
guildId?: string
): Promise<void> {
const scope = guildId ? `servidor ${guildId}` : "globais";
console.log(`🗑️ Deletando todos os comandos ${scope}...`);

const url = guildId
? `${DISCORD_API_URL}/applications/${appId}/guilds/${guildId}/commands`
: `${DISCORD_API_URL}/applications/${appId}/commands`;

try {
// Primeiro, listar comandos existentes
const listResponse = await fetch(url, {
headers: {
"Authorization": `Bot ${botToken}`,
},
});

if (!listResponse.ok) {
throw new Error(`Falha ao listar comandos: ${listResponse.status}`);
}

const existingCommands = await listResponse.json();

if (existingCommands.length === 0) {
console.log(`ℹ️ Nenhum comando ${scope} para deletar.`);
return;
}

console.log(`Encontrados ${existingCommands.length} comandos para deletar`);

// Deletar todos de uma vez enviando array vazio
const deleteResponse = await fetch(url, {
method: "PUT",
headers: {
"Authorization": `Bot ${botToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify([]),
});

if (!deleteResponse.ok) {
const error = await deleteResponse.text();
throw new Error(`Falha ao deletar comandos: ${deleteResponse.status} - ${error}`);
}

console.log(`✅ Todos os comandos ${scope} foram deletados!`);
} catch (error) {
console.error("❌ Erro ao deletar comandos:", error);
throw error;
}
}

// ========================================
// CLI
// ========================================

async function main() {
const appId = process.env.DISCORD_APP_ID;
const botToken = process.env.DISCORD_BOT_TOKEN;
const guildId = process.env.DISCORD_GUILD_ID;

if (!appId || !botToken) {
console.error("❌ Erro: Variáveis de ambiente não configuradas!");
console.error("\nDefina as seguintes variáveis:");
console.error(" - DISCORD_APP_ID: ID da aplicação Discord");
console.error(" - DISCORD_BOT_TOKEN: Token do bot");
console.error(
" - DISCORD_GUILD_ID (opcional): ID do servidor para comandos de teste"
);
console.error("\nExemplo:");
console.error(
" DISCORD_APP_ID=123456 DISCORD_BOT_TOKEN=abc123 bun run scripts/register-commands.ts"
);
process.exit(1);
}

const action = process.argv[2] || "register";

console.log("🤖 Discord Bot - Gerenciador de Comandos\n");

try {
switch (action) {
case "register":
case "reg":
if (guildId) {
console.log("🎯 Modo: Registrar comandos no servidor (instantâneo)");
await registerGuildCommands(appId, botToken, guildId);
} else {
console.log("🌍 Modo: Registrar comandos globais (demora até 1h)");
await registerGlobalCommands(appId, botToken);
}
break;

case "list":
case "ls":
await listCommands(appId, botToken, guildId);
break;

case "delete":
case "del":
await deleteAllCommands(appId, botToken, guildId);
break;

case "help":
case "--help":
case "-h":
console.log("Uso: bun run scripts/register-commands.ts [ação]\n");
console.log("Ações disponíveis:");
console.log(" register, reg Registrar comandos (padrão)");
console.log(" list, ls Listar comandos existentes");
console.log(" delete, del Deletar todos os comandos");
console.log(" help Mostrar esta ajuda");
console.log("\nVariáveis de ambiente:");
console.log(" DISCORD_APP_ID ID da aplicação (obrigatório)");
console.log(" DISCORD_BOT_TOKEN Token do bot (obrigatório)");
console.log(" DISCORD_GUILD_ID ID do servidor (opcional)");
console.log("\nExemplos:");
console.log(" # Registrar globalmente");
console.log(" DISCORD_APP_ID=123 DISCORD_BOT_TOKEN=abc bun run scripts/register-commands.ts");
console.log("\n # Registrar em servidor específico");
console.log(" DISCORD_APP_ID=123 DISCORD_BOT_TOKEN=abc DISCORD_GUILD_ID=456 bun run scripts/register-commands.ts");
console.log("\n # Listar comandos");
console.log(" DISCORD_APP_ID=123 DISCORD_BOT_TOKEN=abc bun run scripts/register-commands.ts list");
break;

default:
console.error(`❌ Ação desconhecida: ${action}`);
console.error("Use 'help' para ver as ações disponíveis.");
process.exit(1);
}

console.log("\n✨ Concluído!");
} catch (error) {
console.error("\n💥 Erro fatal:", error);
process.exit(1);
}
}

// Executar apenas se for o arquivo principal
if (import.meta.main) {
main();
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix formatting to satisfy oxfmt check.

GitHub Actions reports formatting issues in this script. Run the project formatter to keep CI green:

bun run fmt

(and re-run checks). No functional issues spotted in the CLI logic itself.

🧰 Tools
🪛 GitHub Actions: Checks

[error] 1-1: Formatting issues detected by oxfmt. Run 'bun run fmt' (without --check) to fix this file.

🤖 Prompt for AI Agents
In discord-bot/scripts/register-commands.ts lines 1-328 the file fails the
repository formatter (oxfmt/CI) — run the project formatter (bun run fmt) to
automatically fix whitespace/formatting issues, review the changed diff, stage
and commit the formatted file, then push and re-run CI; if the formatter isn't
available ensure the repo's formatting tool is installed/configured (e.g., add
oxfmt/bun fmt to dev setup) and reformat the file before pushing so the GitHub
Actions check passes.

Comment on lines +1146 to +1155
export const listWebhooksInputSchema = z.object({
channelId: z
.string()
.optional()
.describe("ID do canal Discord (se omitido, requer guildId)"),
guildId: z
.string()
.optional()
.describe("ID do servidor Discord (se omitido, requer channelId)"),
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Enforce “at least one of channelId or guildId” for listing webhooks.

listWebhooksInputSchema currently allows both channelId and guildId to be omitted, which will lead to an invalid Discord request. You can encode the “at least one provided” rule directly in the schema:

-export const listWebhooksInputSchema = z.object({
-  channelId: z
-    .string()
-    .optional()
-    .describe("ID do canal Discord (se omitido, requer guildId)"),
-  guildId: z
-    .string()
-    .optional()
-    .describe("ID do servidor Discord (se omitido, requer channelId)"),
-});
+export const listWebhooksInputSchema = z
+  .object({
+    channelId: z
+      .string()
+      .optional()
+      .describe("ID do canal Discord (se omitido, requer guildId)"),
+    guildId: z
+      .string()
+      .optional()
+      .describe("ID do servidor Discord (se omitido, requer channelId)"),
+  })
+  .refine(
+    (data) => !!data.channelId || !!data.guildId,
+    "Forneça pelo menos channelId ou guildId",
+  );

This fails fast on invalid input instead of pushing an unusable request to Discord.


I'll verify the review comment by examining the actual codebase state and checking for usages of this schema.

</script>

🏁 Script executed:

#!/bin/bash
# 1. Check the actual state of the schema in the file
echo "=== Checking listWebhooksInputSchema definition ==="
rg -n "listWebhooksInputSchema" discord-bot/server/lib/types.ts -A 10

# 2. Find all usages of listWebhooksInputSchema across the codebase
echo -e "\n=== Finding all usages of listWebhooksInputSchema ==="
rg -n "listWebhooksInputSchema" --type=ts -g '!node_modules' -C 2

# 3. Check if there's any validation logic in webhook-related handlers
echo -e "\n=== Searching for webhook request handlers ==="
rg -n "webhook" --type=ts -i -g '!node_modules' discord-bot/server -l | head -20

Length of output: 1887


</script>

🏁 Script executed:

#!/bin/bash
# Check the execute function that uses listWebhooksInputSchema
echo "=== Checking the execute function in webhooks.ts ==="
rg -n "execute:" discord-bot/server/tools/webhooks.ts -A 30 | grep -A 30 "inputSchema: listWebhooksInputSchema"

Length of output: 1177


Enforce "at least one of channelId or guildId" at schema level for listing webhooks.

The handler in discord-bot/server/tools/webhooks.ts (lines 202-204) already validates this requirement, but the schema itself should enforce it as well. Move the validation from the handler to the schema using .refine() for consistent, fail-fast validation:

-export const listWebhooksInputSchema = z.object({
-  channelId: z
-    .string()
-    .optional()
-    .describe("ID do canal Discord (se omitido, requer guildId)"),
-  guildId: z
-    .string()
-    .optional()
-    .describe("ID do servidor Discord (se omitido, requer channelId)"),
-});
+export const listWebhooksInputSchema = z
+  .object({
+    channelId: z
+      .string()
+      .optional()
+      .describe("ID do canal Discord (se omitido, requer guildId)"),
+    guildId: z
+      .string()
+      .optional()
+      .describe("ID do servidor Discord (se omitido, requer channelId)"),
+  })
+  .refine(
+    (data) => !!data.channelId || !!data.guildId,
+    "Forneça pelo menos channelId ou guildId",
+  );

This makes the schema self-validating and clarifies the contract at the type level, while the handler validation provides an additional safety layer.

🤖 Prompt for AI Agents
In discord-bot/server/lib/types.ts around lines 1146 to 1155, the input schema
for listing webhooks currently allows both channelId and guildId to be optional
but doesn't enforce that at least one is provided; update the z.object to call
.refine(...) after the object definition to assert that either channelId or
guildId is present (e.g., refine(data => Boolean(data.channelId) ||
Boolean(data.guildId), { message: "Either channelId or guildId must be
provided", path: [...] })), so the schema fails fast and removes the need for
that check in the handler.

Comment on lines +1 to +95
/**
* Verificação de assinaturas de webhooks do Discord
* Usa o algoritmo Ed25519 para validar que as requisições vêm do Discord
*/
import nacl from "tweetnacl";

/**
* Converte string hexadecimal para Uint8Array
*/
function hexToUint8Array(hex: string): Uint8Array {
if (hex.length % 2 !== 0) {
throw new Error("Hex string inválido: comprimento ímpar");
}

const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
}
return bytes;
}

/**
* Verifica a assinatura Ed25519 de uma requisição do Discord
*
* @param body - Corpo da requisição em formato de string (não parseado)
* @param signature - Header X-Signature-Ed25519
* @param timestamp - Header X-Signature-Timestamp
* @param publicKey - Chave pública do Discord (obtida no Developer Portal)
* @returns true se a assinatura é válida, false caso contrário
*/
export async function verifyDiscordSignature(
body: string,
signature: string | null,
timestamp: string | null,
publicKey: string
): Promise<boolean> {
if (!signature || !timestamp) {
console.error("Missing signature or timestamp headers");
return false;
}

try {
// Validar timestamp (rejeitar requisições muito antigas - > 5 minutos)
const now = Math.floor(Date.now() / 1000);
const requestTime = parseInt(timestamp, 10);

if (isNaN(requestTime)) {
console.error("Invalid timestamp format");
return false;
}

const timeDiff = Math.abs(now - requestTime);
if (timeDiff > 300) { // 5 minutos
console.warn(`Request timestamp too old: ${timeDiff}s difference`);
return false;
}

// Construir a mensagem que o Discord assinou
const message = timestamp + body;

// Converter de hexadecimal para bytes
const signatureBytes = hexToUint8Array(signature);
const publicKeyBytes = hexToUint8Array(publicKey);
const messageBytes = new TextEncoder().encode(message);

// Verificar assinatura usando Ed25519
const isValid = nacl.sign.detached.verify(
messageBytes,
signatureBytes,
publicKeyBytes
);

if (!isValid) {
console.error("Invalid signature");
}

return isValid;
} catch (error) {
console.error("Error verifying Discord signature:", error);
return false;
}
}

/**
* Extrai os headers de assinatura de uma requisição
*/
export function extractSignatureHeaders(
request: Request
): { signature: string | null; timestamp: string | null } {
return {
signature: request.headers.get("X-Signature-Ed25519"),
timestamp: request.headers.get("X-Signature-Timestamp"),
};
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Run the formatter to fix oxfmt failures.

CI reports formatting issues for this file. Please run:

bun run fmt

before merging so the oxfmt check passes.

🧰 Tools
🪛 GitHub Actions: Checks

[error] 1-1: Formatting issues detected by oxfmt. Run 'bun run fmt' (without --check) to fix this file.

🤖 Prompt for AI Agents
In discord-bot/server/lib/verification.ts lines 1-95, the file fails the
repository formatter (oxfmt) CI check; run the project formatter to fix
whitespace/formatting issues. Execute the project's formatting command (bun run
fmt) locally or via your preferred npm script, review changes, stage the updated
file, and push the commit so the CI oxfmt check passes.

Comment on lines +5 to +20
import nacl from "tweetnacl";

/**
* Converte string hexadecimal para Uint8Array
*/
function hexToUint8Array(hex: string): Uint8Array {
if (hex.length % 2 !== 0) {
throw new Error("Hex string inválido: comprimento ímpar");
}

const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
}
return bytes;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Confirm tweetnacl import style matches your TypeScript config.

You’re importing tweetnacl as a default export (import nacl from "tweetnacl";), while many setups use import * as nacl from "tweetnacl";. This is fine if esModuleInterop / allowSyntheticDefaultImports are enabled in your tsconfig or Bun config; otherwise you may get a compile-time error.

If your config doesn’t enable synthetic default imports, switch to:

import * as nacl from "tweetnacl";

🏁 Script executed:

#!/bin/bash
# Find TypeScript config files
fd -e json "tsconfig|bun\.config" --type f

# Check package.json for tweetnacl version and dependencies
if [ -f package.json ]; then
  echo "=== package.json tweetnacl entry ===" 
  cat package.json | grep -A2 -B2 "tweetnacl"
fi

Length of output: 289


🏁 Script executed:

#!/bin/bash
# Read tsconfig.json if present
if [ -f tsconfig.json ]; then
  echo "=== tsconfig.json ===" 
  cat tsconfig.json
fi

# Check for bun.config.ts
if [ -f bun.config.ts ]; then
  echo "=== bun.config.ts ===" 
  cat bun.config.ts
fi

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check tweetnacl's package.json exports configuration
if [ -f node_modules/tweetnacl/package.json ]; then
  echo "=== tweetnacl package.json (exports/main) ===" 
  cat node_modules/tweetnacl/package.json | grep -E '"(main|exports|types)"'
fi

# Check if @types/tweetnacl exists
if [ -d node_modules/@types/tweetnacl ]; then
  echo "=== @types/tweetnacl exists ===" 
  ls -la node_modules/@types/tweetnacl/
fi

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Read discord-bot tsconfig.json
echo "=== discord-bot/tsconfig.json ===" 
cat discord-bot/tsconfig.json

echo ""
echo "=== discord-bot/package.json (tweetnacl) ===" 
cat discord-bot/package.json | grep -A3 -B3 "tweetnacl" || echo "tweetnacl not found in discord-bot/package.json"

Length of output: 1140


🏁 Script executed:

#!/bin/bash
# Verify the actual import in verification.ts
echo "=== discord-bot/server/lib/verification.ts (first 25 lines) ===" 
head -25 discord-bot/server/lib/verification.ts

Length of output: 750


🌐 Web query:

tweetnacl JavaScript import default export vs named import

💡 Result:

Short answer: tweetnacl (the npm package) is published as a CommonJS module that exports the library object (module.exports = nacl). In Node / CommonJS use require(...). In ESM you can either use the default-style import made available by many bundlers/transpilers (import nacl from 'tweetnacl') or the namespace form (import * as nacl from 'tweetnacl'); the namespace form always works with CommonJS-to-ESM interop. Examples:

  • CommonJS:
    const nacl = require('tweetnacl'); (github.com)

  • ESM (recommended safe):
    import * as nacl from 'tweetnacl'; (github.com)

  • ESM (default-like; may work depending on bundler/Node interop):
    import nacl from 'tweetnacl'; (github.com)

Why choose import * as nacl: it avoids interop ambiguity between CommonJS module.exports and ESM default imports across different bundlers/runtimes. (github.com)

Citations:


Switch import to namespace form per tweetnacl's recommended practice.

tweetnacl is published as CommonJS and the recommended safe ESM form is import * as nacl from "tweetnacl". Your tsconfig.json uses moduleResolution: "bundler" (Vite) which provides automatic CommonJS interop, so the default import may work at runtime. However, your TypeScript config does not explicitly set esModuleInterop or allowSyntheticDefaultImports, and the namespace form avoids interop ambiguity across different bundlers/runtimes.

Update line 5 in discord-bot/server/lib/verification.ts:

import * as nacl from "tweetnacl";
🤖 Prompt for AI Agents
In discord-bot/server/lib/verification.ts around lines 5 to 20, the file
currently imports tweetnacl using a default import which can cause CommonJS/ESM
interop issues; replace the default import with the namespace form by changing
the import statement to use "import * as nacl from 'tweetnacl'". Ensure no other
code relies on a default export and update any references if needed (they should
remain as nacl.*).

Comment on lines +1 to +327
/**
* Handler para webhooks do Discord
* Processa interações recebidas via webhook
*/
import type { Env } from "../main.ts";
import {
type DiscordInteraction,
InteractionType,
InteractionResponseType,
type InteractionResponse,
} from "./types.ts";
import {
verifyDiscordSignature,
extractSignatureHeaders,
} from "./verification.ts";
import { DISCORD_API_URL } from "./constants.ts";

/**
* Handler principal para requisições de webhook do Discord
*/
export async function handleDiscordWebhook(
request: Request,
env: Env
): Promise<Response> {
// 1. Ler o corpo da requisição (precisa ser string para validação)
const body = await request.text();

// 2. Extrair headers de assinatura
const { signature, timestamp } = extractSignatureHeaders(request);

// 3. Validar assinatura
const state = env.DECO_REQUEST_CONTEXT?.state;

// Suporte para dev local e produção
const publicKey = state?.discordPublicKey || (env as any).DISCORD_PUBLIC_KEY;

if (!publicKey) {
console.error("Discord Public Key not configured");
return new Response("Server configuration error", { status: 500 });
}

const isValid = await verifyDiscordSignature(
body,
signature,
timestamp,
publicKey
);

if (!isValid) {
console.error("Invalid Discord signature");
return new Response("Invalid signature", { status: 401 });
}

// 4. Parsear a interação
let interaction: DiscordInteraction;
try {
interaction = JSON.parse(body) as DiscordInteraction;
} catch (error) {
console.error("Failed to parse interaction:", error);
return new Response("Invalid JSON", { status: 400 });
}

// 5. Processar com base no tipo de interação
return await processInteraction(interaction, env);
}

/**
* Processa diferentes tipos de interação
*/
async function processInteraction(
interaction: DiscordInteraction,
env: Env
): Promise<Response> {
switch (interaction.type) {
case InteractionType.PING:
// Discord está validando o endpoint
return respondToPing();

case InteractionType.APPLICATION_COMMAND:
// Comando de aplicação (slash command)
return await handleApplicationCommand(interaction, env);

case InteractionType.MESSAGE_COMPONENT:
// Interação com componente (botão, select menu, etc)
return await handleMessageComponent(interaction, env);

case InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE:
// Autocomplete de comando
return await handleAutocomplete(interaction, env);

case InteractionType.MODAL_SUBMIT:
// Submissão de modal
return await handleModalSubmit(interaction, env);

default:
console.warn(`Unknown interaction type: ${interaction.type}`);
return new Response("Unknown interaction type", { status: 400 });
}
}

/**
* Responde ao PING do Discord (validação de endpoint)
*/
function respondToPing(): Response {
const response: InteractionResponse = {
type: InteractionResponseType.PONG,
};

return new Response(JSON.stringify(response), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}

/**
* Processa comandos de aplicação (slash commands)
*/
async function handleApplicationCommand(
interaction: DiscordInteraction,
env: Env
): Promise<Response> {
// Responder imediatamente para evitar timeout (3 segundos)
const response: InteractionResponse = {
type: InteractionResponseType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
};

// Processar comando de forma assíncrona
// @ts-ignore - waitUntil existe em Cloudflare Workers
if (env.ctx?.waitUntil) {
// @ts-ignore
env.ctx.waitUntil(processCommandAsync(interaction, env));
} else {
// Fallback para ambientes sem waitUntil
processCommandAsync(interaction, env).catch((error) => {
console.error("Error processing command:", error);
});
}

return new Response(JSON.stringify(response), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}

/**
* Processa o comando de forma assíncrona
*/
async function processCommandAsync(
interaction: DiscordInteraction,
env: Env
): Promise<void> {
try {
const commandName = interaction.data?.name;
const options = interaction.data?.options || [];

// Extrair valor do primeiro argumento (se houver)
const firstOption = options[0];
const userInput = firstOption?.value?.toString() || "";

console.log(`Processing command: ${commandName}`, {
guildId: interaction.guild_id,
channelId: interaction.channel_id,
userId: interaction.member?.user?.id || interaction.user?.id,
input: userInput,
});

// Aqui você pode:
// 1. Buscar configuração do comando em um KV ou D1
// 2. Rotear para um agente Deco específico
// 3. Processar com IA
// 4. Integrar com outros serviços

// Por enquanto, vamos responder com uma mensagem simples
const responseMessage = `Comando **/${commandName}** recebido!\n\n` +
`📝 Input: ${userInput || "(nenhum)"}\n` +
`🆔 Guild: ${interaction.guild_id}\n` +
`📍 Channel: ${interaction.channel_id}`;

// Enviar resposta via followup
await sendFollowupMessage(interaction, env, {
content: responseMessage,
});
} catch (error) {
console.error("Error in processCommandAsync:", error);

// Enviar mensagem de erro
await sendFollowupMessage(interaction, env, {
content: "❌ Ocorreu um erro ao processar o comando.",
});
}
}

/**
* Envia uma mensagem de followup (após resposta inicial)
*/
async function sendFollowupMessage(
interaction: DiscordInteraction,
env: Env,
message: {
content?: string;
embeds?: any[];
components?: any[];
}
): Promise<void> {
const url = `${DISCORD_API_URL}/webhooks/${interaction.application_id}/${interaction.token}`;

// Suporte para dev local e produção
const state = env.DECO_REQUEST_CONTEXT?.state;
const botToken = state?.botToken || (env as any).DISCORD_BOT_TOKEN;

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${botToken}`,
},
body: JSON.stringify(message),
});

if (!response.ok) {
const errorText = await response.text();
console.error("Failed to send followup message:", response.status, errorText);
throw new Error(`Failed to send followup: ${response.status}`);
}
}

/**
* Processa interações com componentes (botões, select menus)
*/
async function handleMessageComponent(
interaction: DiscordInteraction,
_env: Env
): Promise<Response> {
// Responder ao componente
const response: InteractionResponse = {
type: InteractionResponseType.UPDATE_MESSAGE,
data: {
content: `Você clicou no componente: ${interaction.data?.custom_id}`,
},
};

return new Response(JSON.stringify(response), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}

/**
* Processa autocomplete de comandos
*/
async function handleAutocomplete(
_interaction: DiscordInteraction,
_env: Env
): Promise<Response> {
// Retornar opções de autocomplete
const response: InteractionResponse = {
type: InteractionResponseType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT,
data: {
// @ts-ignore - choices não está em InteractionCallbackData mas é válido
choices: [
{ name: "Opção 1", value: "opcao1" },
{ name: "Opção 2", value: "opcao2" },
],
},
};

return new Response(JSON.stringify(response), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}

/**
* Processa submissão de modals
*/
async function handleModalSubmit(
_interaction: DiscordInteraction,
_env: Env
): Promise<Response> {
// Processar dados do modal
const response: InteractionResponse = {
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: "Modal recebido com sucesso!",
},
};

return new Response(JSON.stringify(response), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}

/**
* Edita a resposta original de uma interação
*/
export async function editOriginalResponse(
interaction: DiscordInteraction,
env: Env,
message: {
content?: string;
embeds?: any[];
components?: any[];
}
): Promise<void> {
const url = `${DISCORD_API_URL}/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`;

// Suporte para dev local e produção
const state = env.DECO_REQUEST_CONTEXT?.state;
const botToken = state?.botToken || (env as any).DISCORD_BOT_TOKEN;

const response = await fetch(url, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${botToken}`,
},
body: JSON.stringify(message),
});

if (!response.ok) {
const errorText = await response.text();
console.error("Failed to edit original response:", response.status, errorText);
throw new Error(`Failed to edit response: ${response.status}`);
}
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Format file to satisfy oxfmt.

GitHub Actions indicates formatting issues here. Run:

bun run fmt

so this file conforms to the formatter and CI passes.

🧰 Tools
🪛 GitHub Actions: Checks

[error] 1-1: Formatting issues detected by oxfmt. Run 'bun run fmt' (without --check) to fix this file.

🤖 Prompt for AI Agents
discord-bot/server/lib/webhook-handler.ts lines 1-327: the file fails the
repository formatter (oxfmt) check in CI; run the project's formatter and commit
the updated file to fix whitespace/formatting issues. Execute "bun run fmt" (or
the equivalent project formatting command), review the changes, run lint/tests
if needed, then add/commit the formatted file and push so CI will pass.

Comment on lines +196 to +225
async function sendFollowupMessage(
interaction: DiscordInteraction,
env: Env,
message: {
content?: string;
embeds?: any[];
components?: any[];
}
): Promise<void> {
const url = `${DISCORD_API_URL}/webhooks/${interaction.application_id}/${interaction.token}`;

// Suporte para dev local e produção
const state = env.DECO_REQUEST_CONTEXT?.state;
const botToken = state?.botToken || (env as any).DISCORD_BOT_TOKEN;

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${botToken}`,
},
body: JSON.stringify(message),
});

if (!response.ok) {
const errorText = await response.text();
console.error("Failed to send followup message:", response.status, errorText);
throw new Error(`Failed to send followup: ${response.status}`);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail fast when bot token is missing for followups/edits.

sendFollowupMessage and editOriginalResponse derive botToken from env.DECO_REQUEST_CONTEXT?.state.botToken || (env as any).DISCORD_BOT_TOKEN, but don’t guard against it being absent. In that case you’ll send Authorization: Bot undefined, get a 401 from Discord, and only see it as a generic “failed to send/edit” error.

Consider adding an explicit check:

-  const state = env.DECO_REQUEST_CONTEXT?.state;
-  const botToken = state?.botToken || (env as any).DISCORD_BOT_TOKEN;
+  const state = env.DECO_REQUEST_CONTEXT?.state;
+  const botToken = state?.botToken || (env as any).DISCORD_BOT_TOKEN;
+
+  if (!botToken) {
+    console.error("Discord bot token not configured for followup/edit");
+    throw new Error("Discord bot token not configured");
+  }

This makes configuration issues obvious and avoids confusing downstream errors.

Also applies to: 297-325

🤖 Prompt for AI Agents
In discord-bot/server/lib/webhook-handler.ts around lines 196 to 225 (and
likewise for the editOriginalResponse block at 297-325), the function derives
botToken from env.DECO_REQUEST_CONTEXT?.state.botToken || (env as
any).DISCORD_BOT_TOKEN but does not validate it; add an explicit guard that
checks botToken is a non-empty string and if missing throw a clear, immediate
Error (or return a rejected Promise) like "Missing DISCORD_BOT_TOKEN /
state.botToken for followup/edit operations" before building headers or calling
fetch, so you fail fast with a meaningful message rather than sending an
Authorization: Bot undefined and getting a generic 401 from Discord.

viniciusventura29 and others added 3 commits December 29, 2025 15:26
- Introduced a new workspace for the Discord Bot, including essential configurations in package.json and bun.lock.
- Created the Discord Bot server with tools for managing messages, channels, roles, threads, and webhooks.
- Added TypeScript configuration, Vite setup, and comprehensive documentation for the Discord Bot features and tools.
- Implemented a client for interacting with the Discord API, ensuring robust data handling and validation.
- Included a README.md detailing installation, configuration, and usage instructions for the Discord Bot MCP server.
- Removed outdated comments and documentation from the guilds.ts file to streamline the codebase.
- Renamed tools for fetching user information to include "Discord" in their identifiers for clarity.
- Updated the Env interface to include new methods for managing Discord interactions, enhancing the bot's capabilities.
- Added comprehensive TypeScript types and Zod schemas for input/output validation in the deco.gen.ts file, improving data integrity and consistency across the Discord bot functionalities.
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.

3 participants