Initial commit

This commit is contained in:
BarryY 2025-03-25 12:55:24 +08:00
commit bb79516c97
8 changed files with 2658 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# Node.js
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# Environment Variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Build Output
dist/
build/
# IDE and Editors
.idea/
.vscode/
*.swp
*.swo
# Operating Systems
.DS_Store
Thumbs.db

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 BarryY
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

152
README.md Normal file
View File

@ -0,0 +1,152 @@
# MCP-Discord
A Discord MCP (Model Context Protocol) server that enables AI assistants to interact with the Discord platform.
## Overview
MCP-Discord provides the following Discord-related functionalities:
- Login to Discord bot
- Send messages to specified channels
- Get server information
- Retrieve forum channel lists
- Create/delete/reply to forum posts
- Read channel messages
- Create/delete text channels
- Add/remove message reactions
## Table of Contents
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
- [Tools Documentation](#tools-documentation)
- [Basic Functions](#basic-functions)
- [Channel Management](#channel-management)
- [Forum Functions](#forum-functions)
- [Messages and Reactions](#messages-and-reactions)
- [Development](#development)
- [License](#license)
## Prerequisites
- Node.js (v16.0.0 or higher)
- npm (v7.0.0 or higher)
- A Discord bot with appropriate permissions
- Bot token (obtainable from the [Discord Developer Portal](https://discord.com/developers/applications))
- Message Content Intent enabled
- Server Members Intent enabled
- Presence Intent enabled
- Permissions in your Discord server:
- Send Messages
- Create Public Threads
- Send Messages in Threads
- Manage Threads
- Manage Channels
- Add Reactions
## Installation
```bash
# Clone the repository
git clone https://github.com/barryyip0625/MCP-Discord.git
cd MCP-Discord
# Install dependencies
npm install
# Compile TypeScript
npm run build
```
## Configuration
A Discord bot token is required for proper operation. You can provide it in two ways:
1. Environment variables:
```
DISCORD_TOKEN=your_discord_bot_token
```
2. Using the `--config` parameter when launching:
```
node path/to/MCP-Discord/build/index.js --config "{\"DISCORD_TOKEN\":\"your_discord_bot_token\"}"
```
## Usage with Claude/Cursor
- Claude
```json
{
"mcpServers": {
"discord": {
"command": "node",
"args": [
"path/to/MCP-Discord/build/index.js"
],
"env": {
"DISCORD_TOKEN": "your_discord_bot_token"
}
}
}
}
```
- Cursor
```json
{
"mcpServers": {
"discord": {
"command": "cmd",
"args": [
"/c",
"node",
"path/to/MCP-Discord/build/index.js"
],
"env": {
"DISCORD_TOKEN": "your_discord_bot_token"
}
}
}
}
```
## Tools Documentation
### Basic Functions
- `discord_login`: Login to Discord
- `discord_send`: Send a message to a specified channel
- `discord_get_server_info`: Get Discord server information
### Channel Management
- `discord_create_text_channel`: Create a text channel
- `discord_delete_channel`: Delete a channel
### Forum Functions
- `discord_get_forum_channels`: Get a list of forum channels
- `discord_create_forum_post`: Create a forum post
- `discord_get_forum_post`: Get a forum post
- `discord_reply_to_forum`: Reply to a forum post
- `discord_delete_forum_post`: Delete a forum post
### Messages and Reactions
- `discord_read_messages`: Read channel messages
- `discord_add_reaction`: Add a reaction to a message
- `discord_add_multiple_reactions`: Add multiple reactions to a message
- `discord_remove_reaction`: Remove a reaction from a message
## Development
```bash
# Development mode
npm run dev
```
## License
MIT License

1571
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "mcp-discord",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts"
},
"author": "",
"license": "MIT",
"description": "",
"devDependencies": {
"@types/node": "^20.17.26",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"discord.js": "^14.18.0",
"dotenv": "^16.4.7",
"zod": "^3.24.2"
}
}

845
src/index.ts Normal file
View File

@ -0,0 +1,845 @@
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Client, GatewayIntentBits, Events, TextChannel, ForumChannel, ChannelType } from "discord.js";
// Configuration parsing
let config: any = {};
// Read configuration from environment variables
if (process.env.DISCORD_TOKEN) {
config.DISCORD_TOKEN = process.env.DISCORD_TOKEN;
console.log("Config loaded from environment variables. Discord token available:", !!config.DISCORD_TOKEN);
if (config.DISCORD_TOKEN) {
console.log("Token length:", config.DISCORD_TOKEN.length);
}
} else {
// Try to parse configuration from command line arguments (for backward compatibility)
const configArgIndex = process.argv.indexOf('--config');
if (configArgIndex !== -1 && configArgIndex < process.argv.length - 1) {
try {
let configStr = process.argv[configArgIndex + 1];
// Print raw configuration string for debugging
console.log("Raw config string:", configStr);
// Try to parse JSON
config = JSON.parse(configStr);
console.log("Config parsed successfully. Discord token available:", !!config.DISCORD_TOKEN);
if (config.DISCORD_TOKEN) {
console.log("Token length:", config.DISCORD_TOKEN.length);
}
} catch (error) {
console.error("Failed to parse config argument:", error);
console.error("Raw config argument:", process.argv[configArgIndex + 1]);
// Try to read arguments directly (for debugging)
console.log("All arguments:", process.argv);
}
} else {
console.warn("No config found in environment variables or command line arguments");
console.log("All arguments:", process.argv);
}
}
// Create Discord client
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
// Create an MCP server
const server = new McpServer({
name: "MCP-Discord",
version: "1.0.0"
});
// Add a test tool
server.tool(
"test",
{ name: z.string() },
async () => ({
content: [{ type: "text", text: `test success` }]
})
);
// Discord login tool
server.tool(
"discord_login",
{ random_string: z.string().optional() },
async () => {
try {
const token = config.DISCORD_TOKEN;
if (!token) {
return {
content: [{ type: "text", text: "Discord token not found in config. Make sure the --config parameter is correctly set." }],
isError: true
};
}
await client.login(token);
return {
content: [{ type: "text", text: `Successfully logged in to Discord : ${client.user?.tag}` }]
};
} catch (error) {
return {
content: [{ type: "text", text: `Login failed: ${error}` }],
isError: true
};
}
}
);
// Discord send message tool
server.tool(
"discord_send",
{
channelId: z.string(),
message: z.string()
},
async ({ channelId, message }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const channel = await client.channels.fetch(channelId);
if (!channel || !channel.isTextBased()) {
return {
content: [{ type: "text", text: `Cannot find text channel ID: ${channelId}` }],
isError: true
};
}
// Ensure channel is text-based and can send messages
if ('send' in channel) {
await channel.send(message);
return {
content: [{ type: "text", text: `Message successfully sent to channel ID: ${channelId}` }]
};
} else {
return {
content: [{ type: "text", text: `This channel type does not support sending messages` }],
isError: true
};
}
} catch (error) {
return {
content: [{ type: "text", text: `Send message failed: ${error}` }],
isError: true
};
}
}
);
// Get forum channels tool
server.tool(
"discord_get_forum_channels",
{
guildId: z.string()
},
async ({ guildId }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const guild = await client.guilds.fetch(guildId);
if (!guild) {
return {
content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }],
isError: true
};
}
// Fetch all channels from the guild
const channels = await guild.channels.fetch();
// Filter to get only forum channels
const forumChannels = channels.filter(channel => channel?.type === ChannelType.GuildForum);
if (forumChannels.size === 0) {
return {
content: [{ type: "text", text: `No forum channels found in guild: ${guild.name}` }]
};
}
// Format forum channels information
const forumInfo = forumChannels.map(channel => ({
id: channel.id,
name: channel.name,
topic: channel.topic || "No topic set"
}));
return {
content: [{ type: "text", text: JSON.stringify(forumInfo, null, 2) }]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to fetch forum channels: ${error}` }],
isError: true
};
}
}
);
// Create forum post tool
server.tool(
"discord_create_forum_post",
{
forumChannelId: z.string(),
title: z.string(),
content: z.string(),
tags: z.array(z.string()).optional()
},
async ({ forumChannelId, title, content, tags }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const channel = await client.channels.fetch(forumChannelId);
if (!channel || channel.type !== ChannelType.GuildForum) {
return {
content: [{ type: "text", text: `Channel ID ${forumChannelId} is not a forum channel.` }],
isError: true
};
}
const forumChannel = channel as ForumChannel;
// Get available tags in the forum
const availableTags = forumChannel.availableTags;
let selectedTagIds: string[] = [];
// If tags are provided, find their IDs
if (tags && tags.length > 0) {
selectedTagIds = availableTags
.filter(tag => tags.includes(tag.name))
.map(tag => tag.id);
}
// Create the forum post
const thread = await forumChannel.threads.create({
name: title,
message: {
content: content
},
appliedTags: selectedTagIds.length > 0 ? selectedTagIds : undefined
});
return {
content: [{
type: "text",
text: `Successfully created forum post "${title}" with ID: ${thread.id}`
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to create forum post: ${error}` }],
isError: true
};
}
}
);
// Get forum post (thread) details tool
server.tool(
"discord_get_forum_post",
{
threadId: z.string()
},
async ({ threadId }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const thread = await client.channels.fetch(threadId);
if (!thread || !(thread.isThread())) {
return {
content: [{ type: "text", text: `Cannot find thread with ID: ${threadId}` }],
isError: true
};
}
// Get messages from the thread
const messages = await thread.messages.fetch({ limit: 10 });
const threadDetails = {
id: thread.id,
name: thread.name,
parentId: thread.parentId,
messageCount: messages.size,
createdAt: thread.createdAt,
messages: messages.map(msg => ({
id: msg.id,
content: msg.content,
author: msg.author.tag,
createdAt: msg.createdAt
}))
};
return {
content: [{ type: "text", text: JSON.stringify(threadDetails, null, 2) }]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to fetch forum post: ${error}` }],
isError: true
};
}
}
);
// Reply to forum post tool
server.tool(
"discord_reply_to_forum",
{
threadId: z.string(),
message: z.string()
},
async ({ threadId, message }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const thread = await client.channels.fetch(threadId);
if (!thread || !(thread.isThread())) {
return {
content: [{ type: "text", text: `Cannot find thread with ID: ${threadId}` }],
isError: true
};
}
if (!('send' in thread)) {
return {
content: [{ type: "text", text: `This thread does not support sending messages` }],
isError: true
};
}
// Send the reply
const sentMessage = await thread.send(message);
return {
content: [{
type: "text",
text: `Successfully replied to forum post. Message ID: ${sentMessage.id}`
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to reply to forum post: ${error}` }],
isError: true
};
}
}
);
// Create text channel tool
server.tool(
"discord_create_text_channel",
{
guildId: z.string(),
channelName: z.string(),
topic: z.string().optional()
},
async ({ guildId, channelName, topic }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const guild = await client.guilds.fetch(guildId);
if (!guild) {
return {
content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }],
isError: true
};
}
// Create the text channel
const channel = await guild.channels.create({
name: channelName,
type: ChannelType.GuildText,
topic: topic
});
return {
content: [{
type: "text",
text: `Successfully created text channel "${channelName}" with ID: ${channel.id}`
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to create text channel: ${error}` }],
isError: true
};
}
}
);
// Delete channel tool
server.tool(
"discord_delete_channel",
{
channelId: z.string(),
reason: z.string().optional()
},
async ({ channelId, reason }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const channel = await client.channels.fetch(channelId);
if (!channel) {
return {
content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }],
isError: true
};
}
// Check if channel can be deleted (has delete method)
if (!('delete' in channel)) {
return {
content: [{ type: "text", text: `This channel type does not support deletion or the bot lacks permissions` }],
isError: true
};
}
// Delete the channel
await channel.delete(reason || "Channel deleted via API");
return {
content: [{
type: "text",
text: `Successfully deleted channel with ID: ${channelId}`
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to delete channel: ${error}` }],
isError: true
};
}
}
);
// Read messages from channel tool
server.tool(
"discord_read_messages",
{
channelId: z.string(),
limit: z.number().min(1).max(100).optional().default(50)
},
async ({ channelId, limit }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const channel = await client.channels.fetch(channelId);
if (!channel) {
return {
content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }],
isError: true
};
}
// Check if channel has messages (text channel, thread, etc.)
if (!channel.isTextBased() || !('messages' in channel)) {
return {
content: [{ type: "text", text: `Channel type does not support reading messages` }],
isError: true
};
}
// Fetch messages
const messages = await channel.messages.fetch({ limit });
if (messages.size === 0) {
return {
content: [{ type: "text", text: `No messages found in channel` }]
};
}
// Format messages
const formattedMessages = messages.map(msg => ({
id: msg.id,
content: msg.content,
author: {
id: msg.author.id,
username: msg.author.username,
bot: msg.author.bot
},
timestamp: msg.createdAt,
attachments: msg.attachments.size,
embeds: msg.embeds.length,
replyTo: msg.reference ? msg.reference.messageId : null
})).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
return {
content: [{
type: "text",
text: JSON.stringify({
channelId,
messageCount: formattedMessages.length,
messages: formattedMessages
}, null, 2)
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to read messages: ${error}` }],
isError: true
};
}
}
);
// Get server information tool
server.tool(
"discord_get_server_info",
{
guildId: z.string()
},
async ({ guildId }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const guild = await client.guilds.fetch(guildId);
if (!guild) {
return {
content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }],
isError: true
};
}
// Fetch additional guild data
await guild.fetch();
// Fetch channel information
const channels = await guild.channels.fetch();
// Categorize channels by type
const channelsByType = {
text: channels.filter(c => c?.type === ChannelType.GuildText).size,
voice: channels.filter(c => c?.type === ChannelType.GuildVoice).size,
category: channels.filter(c => c?.type === ChannelType.GuildCategory).size,
forum: channels.filter(c => c?.type === ChannelType.GuildForum).size,
announcement: channels.filter(c => c?.type === ChannelType.GuildAnnouncement).size,
stage: channels.filter(c => c?.type === ChannelType.GuildStageVoice).size,
total: channels.size
};
// Fetch member count
const approximateMemberCount = guild.approximateMemberCount || "unknown";
// Format guild information
const guildInfo = {
id: guild.id,
name: guild.name,
description: guild.description,
icon: guild.iconURL(),
owner: guild.ownerId,
createdAt: guild.createdAt,
memberCount: approximateMemberCount,
channels: channelsByType,
features: guild.features,
premium: {
tier: guild.premiumTier,
subscriptions: guild.premiumSubscriptionCount
}
};
return {
content: [{ type: "text", text: JSON.stringify(guildInfo, null, 2) }]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to fetch server info: ${error}` }],
isError: true
};
}
}
);
// Add reaction to message tool
server.tool(
"discord_add_reaction",
{
channelId: z.string(),
messageId: z.string(),
emoji: z.string()
},
async ({ channelId, messageId, emoji }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const channel = await client.channels.fetch(channelId);
if (!channel || !channel.isTextBased() || !('messages' in channel)) {
return {
content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }],
isError: true
};
}
const message = await channel.messages.fetch(messageId);
if (!message) {
return {
content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }],
isError: true
};
}
// Add the reaction
await message.react(emoji);
return {
content: [{
type: "text",
text: `Successfully added reaction ${emoji} to message ID: ${messageId}`
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to add reaction: ${error}` }],
isError: true
};
}
}
);
// Add multiple reactions to message tool
server.tool(
"discord_add_multiple_reactions",
{
channelId: z.string(),
messageId: z.string(),
emojis: z.array(z.string())
},
async ({ channelId, messageId, emojis }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const channel = await client.channels.fetch(channelId);
if (!channel || !channel.isTextBased() || !('messages' in channel)) {
return {
content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }],
isError: true
};
}
const message = await channel.messages.fetch(messageId);
if (!message) {
return {
content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }],
isError: true
};
}
// Add each reaction sequentially
for (const emoji of emojis) {
await message.react(emoji);
// Small delay to prevent rate limiting
await new Promise(resolve => setTimeout(resolve, 300));
}
return {
content: [{
type: "text",
text: `Successfully added ${emojis.length} reactions to message ID: ${messageId}`
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to add reactions: ${error}` }],
isError: true
};
}
}
);
// Remove reaction from message tool
server.tool(
"discord_remove_reaction",
{
channelId: z.string(),
messageId: z.string(),
emoji: z.string(),
userId: z.string().optional()
},
async ({ channelId, messageId, emoji, userId }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const channel = await client.channels.fetch(channelId);
if (!channel || !channel.isTextBased() || !('messages' in channel)) {
return {
content: [{ type: "text", text: `Cannot find text channel with ID: ${channelId}` }],
isError: true
};
}
const message = await channel.messages.fetch(messageId);
if (!message) {
return {
content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }],
isError: true
};
}
// Get the reactions
const reactions = message.reactions.cache;
// Find the specific reaction
const reaction = reactions.find(r => r.emoji.toString() === emoji || r.emoji.name === emoji);
if (!reaction) {
return {
content: [{ type: "text", text: `Reaction ${emoji} not found on message ID: ${messageId}` }],
isError: true
};
}
if (userId) {
// Remove a specific user's reaction
await reaction.users.remove(userId);
return {
content: [{
type: "text",
text: `Successfully removed reaction ${emoji} from user ID: ${userId} on message ID: ${messageId}`
}]
};
} else {
// Remove bot's reaction
await reaction.users.remove(client.user.id);
return {
content: [{
type: "text",
text: `Successfully removed bot's reaction ${emoji} from message ID: ${messageId}`
}]
};
}
} catch (error) {
return {
content: [{ type: "text", text: `Failed to remove reaction: ${error}` }],
isError: true
};
}
}
);
// Delete forum post tool
server.tool(
"discord_delete_forum_post",
{
threadId: z.string(),
reason: z.string().optional()
},
async ({ threadId, reason }) => {
try {
if (!client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const thread = await client.channels.fetch(threadId);
if (!thread || !thread.isThread()) {
return {
content: [{ type: "text", text: `Cannot find forum post/thread with ID: ${threadId}` }],
isError: true
};
}
// Delete the forum post/thread
await thread.delete(reason || "Forum post deleted via API");
return {
content: [{
type: "text",
text: `Successfully deleted forum post/thread with ID: ${threadId}`
}]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to delete forum post: ${error}` }],
isError: true
};
}
}
);
// Auto-login on startup if token is available
const autoLogin = async () => {
const token = config.DISCORD_TOKEN;
if (token) {
try {
await client.login(token);
} catch (error) {
console.error("Auto-login failed:", error);
}
} else {
console.log("No Discord token found in config, skipping auto-login");
}
};
// Start auto-login process
autoLogin();
const transport = new StdioServerTransport();
await server.connect(transport);

15
tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}