feat: Discord + Instagram integrations
Discord Bot API
- New client: src/clients/discord.ts
- Tools: discord_get_me, discord_get_guilds, discord_get_channels, discord_send_message, discord_get_messages
- REST endpoints: GET /api/discord/me, /api/discord/guilds, /api/discord/channels, /api/discord/messages, POST /api/discord/message
- Multi-account env var: DISCORD_{ACCOUNT}_BOT_TOKEN
Instagram Graph API
- New client: src/clients/instagram.ts
- Tools: instagram_get_profile, instagram_get_media, instagram_create_post
- REST endpoints: GET /api/instagram/profile, /api/instagram/media, POST /api/instagram/post
- Multi-account env vars: INSTAGRAM_{ACCOUNT}_ACCESS_TOKEN, INSTAGRAM_{ACCOUNT}_BUSINESS_ACCOUNT_ID
Total tools: 32
This commit is contained in:
14
.env.example
14
.env.example
@@ -60,3 +60,17 @@ LINKEDIN_DEFAULT_CLIENT_SECRET=your-linkedin-client-secret
|
||||
# For default account:
|
||||
TELEGRAM_DEFAULT_BOT_TOKEN=your-telegram-bot-token
|
||||
# For additional accounts, duplicate with TELEGRAM_{ACCOUNT}_*
|
||||
|
||||
# ── Discord Bot API ──────────────────────────────────────────────────────────
|
||||
# Create a bot at https://discord.com/developers/applications → New Application → Bot → Copy Token
|
||||
# For default account:
|
||||
DISCORD_DEFAULT_BOT_TOKEN=your-discord-bot-token
|
||||
# For additional accounts, duplicate with DISCORD_{ACCOUNT}_*
|
||||
|
||||
# ── Instagram Graph API ──────────────────────────────────────────────────────
|
||||
# Requires Instagram Business/Creator account connected to Facebook Page
|
||||
# Get token from Facebook Developer Console with instagram_basic + instagram_content_publish scopes
|
||||
# For default account:
|
||||
INSTAGRAM_DEFAULT_ACCESS_TOKEN=your-instagram-access-token
|
||||
INSTAGRAM_DEFAULT_BUSINESS_ACCOUNT_ID=your-instagram-business-account-id
|
||||
# For additional accounts, duplicate with INSTAGRAM_{ACCOUNT}_*
|
||||
|
||||
101
src/clients/discord.ts
Normal file
101
src/clients/discord.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
const DISCORD_API_BASE = 'https://discord.com/api/v10';
|
||||
|
||||
function getToken(account: string): string {
|
||||
const envKey = `DISCORD_${account.toUpperCase()}_BOT_TOKEN`;
|
||||
return process.env[envKey] ?? '';
|
||||
}
|
||||
|
||||
async function discordRequest(
|
||||
token: string,
|
||||
endpoint: string,
|
||||
method: 'GET' | 'POST' = 'GET',
|
||||
body?: unknown
|
||||
) {
|
||||
const url = `${DISCORD_API_BASE}${endpoint}`;
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bot ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.text();
|
||||
throw new Error(`Discord API error (${res.status}): ${error}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function getMe(args: { account?: string }): Promise<{
|
||||
id: string;
|
||||
username: string;
|
||||
bot: boolean;
|
||||
}> {
|
||||
const token = getToken(args.account ?? 'default');
|
||||
if (!token) {
|
||||
throw new Error('Missing Discord credentials. Set DISCORD_{ACCOUNT}_BOT_TOKEN');
|
||||
}
|
||||
return discordRequest(token, '/users/@me');
|
||||
}
|
||||
|
||||
export async function getGuilds(args: { account?: string }): Promise<Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
}>> {
|
||||
const token = getToken(args.account ?? 'default');
|
||||
if (!token) {
|
||||
throw new Error('Missing Discord credentials. Set DISCORD_{ACCOUNT}_BOT_TOKEN');
|
||||
}
|
||||
return discordRequest(token, '/users/@me/guilds');
|
||||
}
|
||||
|
||||
export async function getChannels(args: { guild_id: string; account?: string }): Promise<Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
type: number;
|
||||
}>> {
|
||||
const token = getToken(args.account ?? 'default');
|
||||
if (!token) {
|
||||
throw new Error('Missing Discord credentials. Set DISCORD_{ACCOUNT}_BOT_TOKEN');
|
||||
}
|
||||
return discordRequest(token, `/guilds/${args.guild_id}/channels`);
|
||||
}
|
||||
|
||||
export async function sendMessage(args: {
|
||||
channel_id: string;
|
||||
content: string;
|
||||
account?: string;
|
||||
}): Promise<{ id: string; channel_id: string }> {
|
||||
const token = getToken(args.account ?? 'default');
|
||||
if (!token) {
|
||||
throw new Error('Missing Discord credentials. Set DISCORD_{ACCOUNT}_BOT_TOKEN');
|
||||
}
|
||||
|
||||
return discordRequest(token, `/channels/${args.channel_id}/messages`, 'POST', {
|
||||
content: args.content,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getMessages(args: {
|
||||
channel_id: string;
|
||||
limit?: number;
|
||||
account?: string;
|
||||
}): Promise<Array<{
|
||||
id: string;
|
||||
content: string;
|
||||
author: { username: string; id: string };
|
||||
timestamp: string;
|
||||
}>> {
|
||||
const token = getToken(args.account ?? 'default');
|
||||
if (!token) {
|
||||
throw new Error('Missing Discord credentials. Set DISCORD_{ACCOUNT}_BOT_TOKEN');
|
||||
}
|
||||
|
||||
const limit = args.limit ?? 10;
|
||||
return discordRequest(token, `/channels/${args.channel_id}/messages?limit=${limit}`);
|
||||
}
|
||||
139
src/clients/instagram.ts
Normal file
139
src/clients/instagram.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
const INSTAGRAM_API_BASE = 'https://graph.facebook.com/v18.0';
|
||||
|
||||
function getAccessToken(account: string): string {
|
||||
const envKey = `INSTAGRAM_${account.toUpperCase()}_ACCESS_TOKEN`;
|
||||
return process.env[envKey] ?? '';
|
||||
}
|
||||
|
||||
function getBusinessAccountId(account: string): string {
|
||||
const envKey = `INSTAGRAM_${account.toUpperCase()}_BUSINESS_ACCOUNT_ID`;
|
||||
return process.env[envKey] ?? '';
|
||||
}
|
||||
|
||||
async function instagramRequest(
|
||||
endpoint: string,
|
||||
accessToken: string,
|
||||
method: 'GET' | 'POST' = 'GET',
|
||||
params?: Record<string, unknown>
|
||||
) {
|
||||
const url = new URL(`${INSTAGRAM_API_BASE}${endpoint}`);
|
||||
url.searchParams.set('access_token', accessToken);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
method,
|
||||
headers: params ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body: params ? JSON.stringify(params) : undefined,
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const error = await res.text();
|
||||
throw new Error(`Instagram API error (${res.status}): ${error}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function getProfile(args: { account?: string }): Promise<{
|
||||
id: string;
|
||||
username: string;
|
||||
name: string;
|
||||
followers_count: number;
|
||||
follows_count: number;
|
||||
media_count: number;
|
||||
}> {
|
||||
const accessToken = getAccessToken(args.account ?? 'default');
|
||||
const businessAccountId = getBusinessAccountId(args.account ?? 'default');
|
||||
|
||||
if (!accessToken || !businessAccountId) {
|
||||
throw new Error('Missing Instagram credentials. Set INSTAGRAM_{ACCOUNT}_ACCESS_TOKEN and INSTAGRAM_{ACCOUNT}_BUSINESS_ACCOUNT_ID');
|
||||
}
|
||||
|
||||
const data = await instagramRequest(
|
||||
`/${businessAccountId}?fields=username,name,followers_count,follows_count,media_count`,
|
||||
accessToken
|
||||
);
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
username: data.username ?? '',
|
||||
name: data.name ?? '',
|
||||
followers_count: data.followers_count ?? 0,
|
||||
follows_count: data.follows_count ?? 0,
|
||||
media_count: data.media_count ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getMedia(args: { limit?: number; account?: string }): Promise<Array<{
|
||||
id: string;
|
||||
caption?: string;
|
||||
media_type: string;
|
||||
media_url?: string;
|
||||
permalink?: string;
|
||||
timestamp?: string;
|
||||
}>> {
|
||||
const accessToken = getAccessToken(args.account ?? 'default');
|
||||
const businessAccountId = getBusinessAccountId(args.account ?? 'default');
|
||||
|
||||
if (!accessToken || !businessAccountId) {
|
||||
throw new Error('Missing Instagram credentials. Set INSTAGRAM_{ACCOUNT}_ACCESS_TOKEN and INSTAGRAM_{ACCOUNT}_BUSINESS_ACCOUNT_ID');
|
||||
}
|
||||
|
||||
const limit = args.limit ?? 10;
|
||||
const data = await instagramRequest(
|
||||
`/${businessAccountId}/media?fields=id,caption,media_type,media_url,permalink,timestamp&limit=${limit}`,
|
||||
accessToken
|
||||
);
|
||||
|
||||
return (data.data ?? []).map((item: { id: string; caption?: string; media_type: string; media_url?: string; permalink?: string; timestamp?: string }) => ({
|
||||
id: item.id,
|
||||
caption: item.caption,
|
||||
media_type: item.media_type,
|
||||
media_url: item.media_url,
|
||||
permalink: item.permalink,
|
||||
timestamp: item.timestamp,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function createPost(args: {
|
||||
image_url: string;
|
||||
caption?: string;
|
||||
account?: string;
|
||||
}): Promise<{ success: boolean; media_id: string }> {
|
||||
const accessToken = getAccessToken(args.account ?? 'default');
|
||||
const businessAccountId = getBusinessAccountId(args.account ?? 'default');
|
||||
|
||||
if (!accessToken || !businessAccountId) {
|
||||
throw new Error('Missing Instagram credentials. Set INSTAGRAM_{ACCOUNT}_ACCESS_TOKEN and INSTAGRAM_{ACCOUNT}_BUSINESS_ACCOUNT_ID');
|
||||
}
|
||||
|
||||
// Step 1: Create media container
|
||||
const container = await instagramRequest(
|
||||
`/${businessAccountId}/media`,
|
||||
accessToken,
|
||||
'POST',
|
||||
{
|
||||
image_url: args.image_url,
|
||||
caption: args.caption,
|
||||
media_type: 'REELS',
|
||||
}
|
||||
);
|
||||
|
||||
const creationId = container.id;
|
||||
if (!creationId) {
|
||||
throw new Error('Failed to create Instagram media container');
|
||||
}
|
||||
|
||||
// Step 2: Publish the container
|
||||
const publish = await instagramRequest(
|
||||
`/${businessAccountId}/media_publish`,
|
||||
accessToken,
|
||||
'POST',
|
||||
{ creation_id: creationId }
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
media_id: publish.id,
|
||||
};
|
||||
}
|
||||
90
src/index.ts
90
src/index.ts
@@ -679,6 +679,96 @@ app.get('/api/telegram/chat', requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── Discord REST endpoints ──────────────────────────────────────
|
||||
app.get('/api/discord/me', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_me', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/discord/guilds', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_guilds', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/discord/channels', requireAuth, async (req, res) => {
|
||||
const guild_id = req.query.guild_id as string | undefined;
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!guild_id) { res.status(400).json({ error: 'guild_id is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_channels', { guild_id, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/discord/message', requireAuth, async (req, res) => {
|
||||
const { channel_id, content, account } = req.body as Record<string, unknown>;
|
||||
if (!channel_id || !content) { res.status(400).json({ error: 'channel_id and content are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('discord_send_message', { channel_id, content, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/discord/messages', requireAuth, async (req, res) => {
|
||||
const channel_id = req.query.channel_id as string | undefined;
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!channel_id) { res.status(400).json({ error: 'channel_id is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_messages', { channel_id, limit, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Instagram REST endpoints ────────────────────────────────────
|
||||
app.get('/api/instagram/profile', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('instagram_get_profile', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/instagram/media', requireAuth, async (req, res) => {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('instagram_get_media', { limit, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/instagram/post', requireAuth, async (req, res) => {
|
||||
const { image_url, caption, account } = req.body as Record<string, unknown>;
|
||||
if (!image_url) { res.status(400).json({ error: 'image_url is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('instagram_create_post', { image_url, caption, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/pilot-request', async (req, res) => {
|
||||
const origin = req.get('origin');
|
||||
if (origin && !SQUAREMCP_ALLOWED_ORIGINS.has(origin)) {
|
||||
|
||||
216
src/manifest.ts
216
src/manifest.ts
@@ -703,6 +703,214 @@ export function getManifest(serverUrl: string, authEnabled: boolean) {
|
||||
examples: [{ chat_id: '@mychannel', account: 'default' }],
|
||||
},
|
||||
|
||||
// ── Discord tools ──────────────────────────────────────────────────────
|
||||
{
|
||||
name: 'discord_get_me',
|
||||
category: 'discord',
|
||||
description: 'Get information about the Discord bot',
|
||||
when_to_use:
|
||||
'User asks about the Discord bot, wants to verify it is connected, or needs the bot username.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Bot user ID' },
|
||||
username: { type: 'string' },
|
||||
bot: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
examples: [{ account: 'default' }],
|
||||
},
|
||||
{
|
||||
name: 'discord_get_guilds',
|
||||
category: 'discord',
|
||||
description: 'List Discord servers the bot is in',
|
||||
when_to_use:
|
||||
'User wants to know which Discord servers are available or needs a server ID.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Guild ID' },
|
||||
name: { type: 'string', description: 'Server name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
examples: [{ account: 'default' }],
|
||||
},
|
||||
{
|
||||
name: 'discord_get_channels',
|
||||
category: 'discord',
|
||||
description: 'List channels in a Discord server',
|
||||
when_to_use:
|
||||
'User needs to find a channel ID to send a message to.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
required: ['guild_id'],
|
||||
properties: {
|
||||
guild_id: { type: 'string', description: 'Discord server (guild) ID' },
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Channel ID' },
|
||||
name: { type: 'string' },
|
||||
type: { type: 'number', description: 'Channel type code' },
|
||||
},
|
||||
},
|
||||
},
|
||||
examples: [{ guild_id: '1234567890123456789', account: 'default' }],
|
||||
},
|
||||
{
|
||||
name: 'discord_send_message',
|
||||
category: 'discord',
|
||||
description: 'Send a message to a Discord channel',
|
||||
when_to_use:
|
||||
'User wants to post a message in a Discord server channel.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
required: ['channel_id', 'content'],
|
||||
properties: {
|
||||
channel_id: { type: 'string', description: 'Discord channel ID' },
|
||||
content: { type: 'string', description: 'Message content (max 2000 chars)' },
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Message ID' },
|
||||
channel_id: { type: 'string' },
|
||||
},
|
||||
},
|
||||
examples: [{ channel_id: '1234567890123456789', content: 'Hello Discord!', account: 'default' }],
|
||||
},
|
||||
{
|
||||
name: 'discord_get_messages',
|
||||
category: 'discord',
|
||||
description: 'Get recent messages from a Discord channel',
|
||||
when_to_use:
|
||||
'User wants to read recent chat history from a Discord channel.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
required: ['channel_id'],
|
||||
properties: {
|
||||
channel_id: { type: 'string', description: 'Discord channel ID' },
|
||||
limit: { type: 'number', description: 'Max messages (default: 10, max: 100)' },
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Message ID' },
|
||||
content: { type: 'string' },
|
||||
author: { type: 'object', properties: { username: { type: 'string' }, id: { type: 'string' } } },
|
||||
timestamp: { type: 'string', format: 'date-time' },
|
||||
},
|
||||
},
|
||||
},
|
||||
examples: [{ channel_id: '1234567890123456789', limit: 10, account: 'default' }],
|
||||
},
|
||||
|
||||
// ── Instagram tools ────────────────────────────────────────────────────
|
||||
{
|
||||
name: 'instagram_get_profile',
|
||||
category: 'instagram',
|
||||
description: 'Get Instagram Business/Creator account profile',
|
||||
when_to_use:
|
||||
'User asks about their Instagram stats, followers, or account details.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which Instagram account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
followers_count: { type: 'number' },
|
||||
follows_count: { type: 'number' },
|
||||
media_count: { type: 'number' },
|
||||
},
|
||||
},
|
||||
examples: [{ account: 'default' }],
|
||||
},
|
||||
{
|
||||
name: 'instagram_get_media',
|
||||
category: 'instagram',
|
||||
description: 'Get recent posts from Instagram',
|
||||
when_to_use:
|
||||
'User wants to see their recent Instagram content.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max posts (default: 10)' },
|
||||
account: { type: 'string', description: 'Which Instagram account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
caption: { type: 'string' },
|
||||
media_type: { type: 'string' },
|
||||
media_url: { type: 'string' },
|
||||
permalink: { type: 'string' },
|
||||
timestamp: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
examples: [{ limit: 5, account: 'default' }],
|
||||
},
|
||||
{
|
||||
name: 'instagram_create_post',
|
||||
category: 'instagram',
|
||||
description: 'Create a post on Instagram',
|
||||
when_to_use:
|
||||
'User wants to publish an image to their Instagram Business/Creator account.',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
required: ['image_url'],
|
||||
properties: {
|
||||
image_url: { type: 'string', description: 'Publicly accessible image URL' },
|
||||
caption: { type: 'string', description: 'Post caption' },
|
||||
account: { type: 'string', description: 'Which Instagram account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean' },
|
||||
media_id: { type: 'string' },
|
||||
},
|
||||
},
|
||||
examples: [{ image_url: 'https://example.com/photo.jpg', caption: 'New post!', account: 'default' }],
|
||||
},
|
||||
|
||||
// ── Obsidian tools ──────────────────────────────────────────────────────
|
||||
{
|
||||
name: 'obsidian_search_notes',
|
||||
@@ -874,6 +1082,14 @@ export function getManifest(serverUrl: string, authEnabled: boolean) {
|
||||
description: 'Telegram messaging via Bot API',
|
||||
icon: '✈️',
|
||||
},
|
||||
discord: {
|
||||
description: 'Discord server messaging via Bot API',
|
||||
icon: '🎮',
|
||||
},
|
||||
instagram: {
|
||||
description: 'Instagram Business/Creator account via Graph API',
|
||||
icon: '📸',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
164
src/tools.ts
164
src/tools.ts
@@ -5,6 +5,8 @@ import { searchNotes, getNote, appendToNote, updateNote, getSyncStatus } from '.
|
||||
import { sendMessage, sendTemplate, getMessageStatus, listTemplates } from './clients/whatsapp.js';
|
||||
import { getProfile as getLinkedInProfile, createPost as createLinkedInPost, searchConnections, sendMessage as sendLinkedInMessage } from './clients/linkedin.js';
|
||||
import { getMe as getTelegramMe, sendMessage as sendTelegramMessage, sendPhoto as sendTelegramPhoto, getUpdates as getTelegramUpdates, getChat as getTelegramChat } from './clients/telegram.js';
|
||||
import { getMe as getDiscordMe, getGuilds, getChannels, sendMessage as sendDiscordMessage, getMessages as getDiscordMessages } from './clients/discord.js';
|
||||
import { getProfile as getInstagramProfile, getMedia as getInstagramMedia, createPost as createInstagramPost } from './clients/instagram.js';
|
||||
|
||||
const ACCOUNT_PARAM = {
|
||||
account: {
|
||||
@@ -353,6 +355,110 @@ export const tools: Tool[] = [
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ── Discord tools ────────────────────────────────────────────
|
||||
{
|
||||
name: 'discord_get_me',
|
||||
description:
|
||||
'Get information about the Discord bot. Use to verify the bot is connected and get its username.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'discord_get_guilds',
|
||||
description:
|
||||
'List Discord servers (guilds) the bot is a member of. Use to find server IDs for sending messages.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'discord_get_channels',
|
||||
description:
|
||||
'List channels in a Discord server. Use to find channel IDs for sending messages.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
required: ['guild_id'],
|
||||
properties: {
|
||||
guild_id: { type: 'string', description: 'Discord server (guild) ID' },
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'discord_send_message',
|
||||
description:
|
||||
'Send a message to a Discord channel. Use when the user wants to post in a Discord server.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
required: ['channel_id', 'content'],
|
||||
properties: {
|
||||
channel_id: { type: 'string', description: 'Discord channel ID' },
|
||||
content: { type: 'string', description: 'Message content (up to 2000 chars)' },
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'discord_get_messages',
|
||||
description:
|
||||
'Get recent messages from a Discord channel. Use to read chat history.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
required: ['channel_id'],
|
||||
properties: {
|
||||
channel_id: { type: 'string', description: 'Discord channel ID' },
|
||||
limit: { type: 'number', description: 'Max messages to return (default: 10, max: 100)' },
|
||||
account: { type: 'string', description: 'Which Discord account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ── Instagram tools ──────────────────────────────────────────
|
||||
{
|
||||
name: 'instagram_get_profile',
|
||||
description:
|
||||
'Get Instagram Business/Creator account profile info. Use when the user asks about their Instagram stats, followers, or account details.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which Instagram account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'instagram_get_media',
|
||||
description:
|
||||
'Get recent posts from an Instagram Business/Creator account. Use when the user wants to see their recent content.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max posts to return (default: 10)' },
|
||||
account: { type: 'string', description: 'Which Instagram account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'instagram_create_post',
|
||||
description:
|
||||
'Create a post on Instagram. [REQUIRES BUSINESS ACCOUNT] Only works with Instagram Business/Creator accounts connected to a Facebook Page.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
required: ['image_url'],
|
||||
properties: {
|
||||
image_url: { type: 'string', description: 'Publicly accessible URL of the image to post' },
|
||||
caption: { type: 'string', description: 'Post caption text' },
|
||||
account: { type: 'string', description: 'Which Instagram account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function acct(args: Record<string, unknown>): Account {
|
||||
@@ -525,6 +631,64 @@ export async function handleToolCall(
|
||||
});
|
||||
break;
|
||||
|
||||
// ── Discord ─────────────────────────────────────────────────
|
||||
case 'discord_get_me':
|
||||
result = await getDiscordMe({
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'discord_get_guilds':
|
||||
result = await getGuilds({
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'discord_get_channels':
|
||||
result = await getChannels({
|
||||
guild_id: args.guild_id as string,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'discord_send_message':
|
||||
result = await sendDiscordMessage({
|
||||
channel_id: args.channel_id as string,
|
||||
content: args.content as string,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'discord_get_messages':
|
||||
result = await getDiscordMessages({
|
||||
channel_id: args.channel_id as string,
|
||||
limit: (args.limit as number) ?? 10,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
// ── Instagram ───────────────────────────────────────────────
|
||||
case 'instagram_get_profile':
|
||||
result = await getInstagramProfile({
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'instagram_get_media':
|
||||
result = await getInstagramMedia({
|
||||
limit: (args.limit as number) ?? 10,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'instagram_create_post':
|
||||
result = await createInstagramPost({
|
||||
image_url: args.image_url as string,
|
||||
caption: args.caption as string | undefined,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
// Legacy Yahoo-prefixed names — keep working for any cached Claude sessions
|
||||
case 'yahoo_get_profile':
|
||||
result = await getProfile('yahoo');
|
||||
|
||||
Reference in New Issue
Block a user