Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

# Remote MCP πŸš€

**Create and connect MCP servers to your favorite AI clients - no complex setup required!**
**Easily create and connect MCP servers to your favorite AI clients β€” no complex setup required!**

</div>

[🌐 Try Remote MCP](https://remotemcp.tech) | [πŸ“– What is MCP?](#what-is-mcp) | [πŸš€ Getting Started](#getting-started)
[🌐 Try Remote MCP](https://remotemcp.tech) | [πŸ“– What is MCP?](#what-is-mcp) | [πŸ’‘ Why Remote MCP?](#why-remote-mcp) | [πŸ“± Available Apps](#available-apps) | [πŸš€ Getting Started](#getting-started)

---

Expand All @@ -17,11 +17,7 @@

Remote MCP is a cloud-based platform that lets you easily create and manage **Model Context Protocol (MCP) servers** and connect them to your favorite AI clients like Claude Desktop, Cursor, or any MCP-compatible application.

Think of it as a bridge between your AI assistant and the apps you use every day - GitHub, Slack, YouTube, PostgreSQL, and many more!



https://github.com/user-attachments/assets/3ddedf4e-571b-4e78-a46c-f15df8c4fe56
Think of it as a bridge between your AI assistant and the apps you use every day β€” GitHub, Slack, YouTube, PostgreSQL, and many more!


## What is MCP?
Expand All @@ -37,7 +33,7 @@ The **Model Context Protocol (MCP)** is an open standard that enables AI assista

### How MCP Works

```
```text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ AI Client │────│ MCP Server │────│ Your App β”‚
β”‚ (Claude) β”‚ β”‚ (Remote MCP)β”‚ β”‚ (GitHub) β”‚
Expand All @@ -46,7 +42,7 @@ The **Model Context Protocol (MCP)** is an open standard that enables AI assista

1. **AI Client**: Your favorite AI assistant (Claude Desktop, Cursor, etc.)
2. **MCP Server**: Acts as a bridge (that's what Remote MCP provides!)
3. **Your App**: The service you want to connect (GitHub, Slack, etc.)
3. **Your App**: The service you want to connect to (GitHub, Slack, etc.)

## Why Remote MCP?

Expand All @@ -56,15 +52,15 @@ No need to run local servers or manage complex configurations. Just create, conf

### πŸ”’ **Secure & Reliable**

Your credentials are encrypted and managed securely. We handle authentication, API limits, and security.
Your credentials are encrypted and managed securely. We handle authentication, API rate limits, and security best practices.

### 🌍 **Always Available**

Cloud-hosted servers that work 24/7, accessible from any MCP client.

### πŸ“Š **Visual Management**

Easy-to-use dashboard to manage your servers, connections, and monitor usage.
Easy-to-use dashboard to manage your servers, connections, and monitor usage in real-time.


## Available Apps
Expand All @@ -78,6 +74,7 @@ Remote MCP supports integration with popular apps and services:

### Communication & Productivity
- **<img src="https://cdn.jsdelivr.net/npm/simple-icons@v13/icons/slack.svg" width="16" height="16" style="vertical-align: middle;"> Slack** - Send messages, search conversations, and manage channels (5 tools)
- **<img src="https://cdn.jsdelivr.net/npm/simple-icons@v13/icons/discord.svg" width="16" height="16" style="vertical-align: middle;"> Discord** - Send messages, manage servers, channels, and reactions (6 tools)
- **<img src="https://cdn.jsdelivr.net/npm/simple-icons@v13/icons/notion.svg" width="16" height="16" style="vertical-align: middle;"> Notion** - Database queries, page management, and content creation (18 tools)
- **<img src="https://cdn.jsdelivr.net/npm/simple-icons@v13/icons/spotify.svg" width="16" height="16" style="vertical-align: middle;"> Spotify** - Music playback control, playlist management, and discovery (19 tools)
- **<img src="https://cdn.jsdelivr.net/npm/simple-icons@v13/icons/googledrive.svg" width="16" height="16" style="vertical-align: middle;"> Google Drive** - File management and Google Sheets operations (10 tools)
Expand All @@ -92,18 +89,18 @@ Remote MCP supports integration with popular apps and services:
### Utilities
- **<img src="https://cdn.jsdelivr.net/npm/simple-icons@v13/icons/fastapi.svg" width="16" height="16" style="vertical-align: middle;"> Fetch** - HTTP requests and web content fetching in multiple formats (4 tools)

> **Total: 159+ tools across 11 integrated applications**
> **Total: 165+ tools across 12 integrated applications**

_New apps and tools are being added regularly! Have a specific integration in mind? Let us know!_
_New apps and tools are being added regularly! Have a specific integration in mind? [Let us know!](https://remotemcp.tech)_

## Getting Started

### 1. Create Your MCP Server

1. Visit [remotemcp.tech](https://remotemcp.tech)
2. Sign up with Google or GitHub
2. Sign up with Google or GitHub (it's quick and free!)
3. Click "Add Server" to create your first MCP server
4. Choose the apps you want to connect (GitHub, Slack, etc.)
4. Choose the apps you want to connect (GitHub, Slack, Discord, etc.)

### 2. Configure App Connections

Expand All @@ -117,7 +114,7 @@ _New apps and tools are being added regularly! Have a specific integration in mi
Add your Remote MCP server to your AI client:

**For VS Code & Cursor:**
Simply click the **"Add to VS Code"** or **"Add to Cursor"** button in your server dashboard - it automatically configures everything for you!
Simply click the **"Add to VS Code"** or **"Add to Cursor"** button in your server dashboard β€” it automatically configures everything for you!

**For Claude Desktop:**
Add to your `claude_desktop_config.json`:
Expand All @@ -136,7 +133,7 @@ Add to your `claude_desktop_config.json`:
}
```

**For Other Clients:**
**For Other MCP Clients:**
Use the MCP endpoint URL: `https://remotemcp.tech/api/mcp/YOUR_SERVER_ID`

### 4. Start Using!
Expand All @@ -150,6 +147,7 @@ Your AI assistant can now interact with your connected apps. Try asking:

**Communication & Content:**
- "Send a message to the #general Slack channel"
- "Post an announcement in my Discord server"
- "Create a new page in my Notion workspace"
- "Search for recent videos about AI on YouTube"

Expand All @@ -164,7 +162,7 @@ We welcome contributions! Check out our [contributing guidelines](CONTRIBUTING.m

## License

This project is licensed under the MIT License - see the [MIT](LICENSE) file for details.
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.

---

Expand Down
9 changes: 9 additions & 0 deletions src/app/mcp/apps/discord/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { McpAppAuth } from "../../mcp-app/property";

export const discordAuth = McpAppAuth.OAuth2({
description: "Connect to Discord to send messages and manage channels",
authUrl: "https://discord.com/api/oauth2/authorize",
tokenUrl: "https://discord.com/api/oauth2/token",
required: true,
scope: ["bot", "messages.read", "guilds", "guilds.members.read", "identify"],
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

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

The OAuth scopes configured here may not work as intended for user OAuth flows. The "bot" scope is not a valid Discord user OAuth scope - it's used for bot applications.

For user OAuth2 flows, Discord does not allow users to send messages to guild channels via the API using user tokens. The /channels/{channel_id}/messages POST endpoint requires either:

  1. A bot token with appropriate permissions in the guild
  2. Or user OAuth tokens can only send DMs

Consider either:

  • Changing to bot authentication pattern (using bot tokens instead of user OAuth)
  • Or limiting functionality to DM-only capabilities and removing guild channel message sending
  • Or documenting that this requires a bot application setup, not standard user OAuth

Valid user OAuth scopes would be: identify, email, guilds, guilds.join, guilds.members.read, gdm.join, messages.read (for DMs only).

Suggested change
scope: ["bot", "messages.read", "guilds", "guilds.members.read", "identify"],
scope: ["messages.read", "guilds", "guilds.members.read", "identify"],

Copilot uses AI. Check for mistakes.
});
211 changes: 211 additions & 0 deletions src/app/mcp/apps/discord/discord-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import type { OAuth2Props } from "@/app/mcp/mcp-app/property";
import type { OAuth2Property } from "@/app/mcp/mcp-app/property/authentication/oauth2-prop";
import type { McpRequestHandlerExtra } from "../../mcp-app/tools";
import type {
DiscordChannel,
DiscordEmbed,
DiscordGuild,
DiscordMessage,
DiscordMessagePayload,
DiscordUser,
} from "./types";

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

/**
* Discord API client using OAuth2 access token
*/
export class DiscordClient {
private accessToken: string;

constructor(accessToken: string) {
this.accessToken = accessToken;
}

private async makeRequest<T = unknown>(
endpoint: string,
options: RequestInit = {},
): Promise<T> {
const url = `${DISCORD_API_BASE}${endpoint}`;
const headers = {
Authorization: `Bearer ${this.accessToken}`,
"Content-Type": "application/json",
...options.headers,
};

const response = await fetch(url, {
...options,
headers,
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Discord API Error (${response.status}): ${errorText}`);
}

Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

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

The makeRequest method will fail for Discord API endpoints that return 204 No Content responses (such as adding/removing reactions). When the response status is 204, there is no body to parse, so response.json() will throw an error.

Update the method to handle empty responses:

if (!response.ok) {
  const errorText = await response.text();
  throw new Error(`Discord API Error (${response.status}): ${errorText}`);
}

// Handle 204 No Content or other empty responses
if (response.status === 204 || response.headers.get('content-length') === '0') {
  return undefined as T;
}

return response.json();

This affects the addReaction and removeReaction methods which use PUT and DELETE endpoints that return 204.

Suggested change
// Handle 204 No Content or other empty responses
if (response.status === 204 || response.headers.get("content-length") === "0") {
return undefined as T;
}

Copilot uses AI. Check for mistakes.
return response.json();
}

/**
* Get current user information
*/
async getCurrentUser(): Promise<DiscordUser> {
return this.makeRequest<DiscordUser>("/users/@me");
}

/**
* Get user's guilds (servers)
*/
async getUserGuilds(): Promise<DiscordGuild[]> {
return this.makeRequest<DiscordGuild[]>("/users/@me/guilds");
}

/**
* Get guild channels
*/
async getGuildChannels(guildId: string): Promise<DiscordChannel[]> {
return this.makeRequest<DiscordChannel[]>(`/guilds/${guildId}/channels`);
}

/**
* Get channel information
*/
async getChannel(channelId: string): Promise<DiscordChannel> {
return this.makeRequest<DiscordChannel>(`/channels/${channelId}`);
}

/**
* Send a message to a channel
*/
async sendMessage(
channelId: string,
content: string,
embeds?: DiscordEmbed[],
): Promise<DiscordMessage> {
const payload: DiscordMessagePayload = { content };
if (embeds && embeds.length > 0) {
payload.embeds = embeds;
}

return this.makeRequest<DiscordMessage>(`/channels/${channelId}/messages`, {
method: "POST",
body: JSON.stringify(payload),
});
}

/**
* Get messages from a channel
*/
async getChannelMessages(
channelId: string,
options: {
limit?: number;
before?: string;
after?: string;
around?: string;
} = {},
): Promise<DiscordMessage[]> {
const params = new URLSearchParams();
if (options.limit) params.append("limit", options.limit.toString());
if (options.before) params.append("before", options.before);
if (options.after) params.append("after", options.after);
if (options.around) params.append("around", options.around);

const query = params.toString();
const endpoint = `/channels/${channelId}/messages${query ? `?${query}` : ""}`;

return this.makeRequest<DiscordMessage[]>(endpoint);
}

/**
* Add reaction to a message
*/
async addReaction(
channelId: string,
messageId: string,
emoji: string,
): Promise<void> {
return this.makeRequest<void>(
`/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me`,
{ method: "PUT" },
);
}

/**
* Remove reaction from a message
*/
async removeReaction(
channelId: string,
messageId: string,
emoji: string,
): Promise<void> {
return this.makeRequest<void>(
`/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me`,
{ method: "DELETE" },
);
}

/**
* Get guild members
*/
async getGuildMembers(guildId: string, limit = 100): Promise<DiscordUser[]> {
return this.makeRequest<DiscordUser[]>(
`/guilds/${guildId}/members?limit=${limit}`,
);
}
}

/**
* Creates a Discord client using the OAuth2 access token
*/
export function createDiscordClient(
extra: McpRequestHandlerExtra<OAuth2Property<OAuth2Props>>,
): DiscordClient {
if (!extra.auth?.access_token) {
throw new Error("No access token available for Discord API");
}

return new DiscordClient(extra.auth.access_token);
}

/**
* Format error messages from Discord API responses
*/
export function formatDiscordError(error: unknown): string {
if (error instanceof Error) {
return `Discord API Error: ${error.message}`;
}

return `Discord API Error: ${String(error)}`;
}

/**
* Parse channel ID to handle different formats
*/
export function parseChannelId(channelId: string): string {
// Remove # prefix if present
if (channelId.startsWith("#")) {
return channelId.slice(1);
}
return channelId;
}

/**
* Format a Discord message for display
*/
export function formatDiscordMessage(message: DiscordMessage) {
return {
id: message.id,
content: message.content,
author: {
id: message.author.id,
username: message.author.username,
discriminator: message.author.discriminator,
},
timestamp: message.timestamp,
channel_id: message.channel_id,
guild_id: message.guild_id,
embeds: message.embeds,
attachments: message.attachments,
};
}
17 changes: 17 additions & 0 deletions src/app/mcp/apps/discord/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createMcpApp } from "../../mcp-app";
import { McpAppCategory } from "../../mcp-app/app-metadata";
import { discordAuth } from "./common";
import { discordTools } from "./tools";

export const discordMcpApp = createMcpApp({
name: "discord",
displayName: "Discord",
description: "Discord MCP App for accessing Discord servers and channels",
logo: {
type: "url",
url: "https://api.iconify.design/logos:discord-icon.svg",
},
categories: [McpAppCategory.COMMUNICATION],
auth: discordAuth,
tools: discordTools,
});
Loading
Loading