Files
hermes-mcp/src/manifest.ts
Garfield 7796de12bf fix: TikTok API endpoints and privacy level handling
- Fix publish endpoint: /post/video/init/ → /post/publish/video/init/
- Replace broken /video/list/ with /post/publish/creator_info/query/
- Auto-select valid privacy level from creator options (sandbox fix)
- Update tools, manifest, REST routes for creator_info
2026-05-12 00:53:55 -04:00

2087 lines
77 KiB
TypeScript

const SCHEMA_VERSION = '1.0.0';
export function getOpenApiSpec(serverUrl: string) {
return {
openapi: '3.1.0',
info: {
title: 'Hermes',
description: 'Personal AI tools: Obsidian vault (create, read, update, search notes) and email operations across multiple accounts.',
version: '1.0.0',
},
servers: [{ url: serverUrl }],
security: [{ bearerAuth: [] }],
components: {
schemas: {},
securitySchemes: {
bearerAuth: { type: 'http', scheme: 'bearer' },
},
},
paths: {
'/api/obsidian/search': {
get: {
operationId: 'obsidian_search_notes',
summary: 'Search Obsidian notes',
description: 'Full-text search across the vault by content, title, or tags. Returns matching note paths, excerpts, and metadata.',
parameters: [
{ name: 'query', in: 'query', required: true, schema: { type: 'string' }, description: 'Text to search for in note content or title' },
{ name: 'limit', in: 'query', schema: { type: 'integer', default: 10 }, description: 'Maximum number of results to return' },
{ name: 'path_filter', in: 'query', schema: { type: 'string' }, description: 'Only return notes whose path contains this string (e.g. "Daily Notes" or "SquareMCP")' },
{ name: 'tags', in: 'query', schema: { type: 'string' }, description: 'Comma-separated list of tags all results must have' },
],
responses: {
'200': {
description: 'Matching notes',
content: {
'application/json': {
schema: {
type: 'array',
items: {
type: 'object',
properties: {
path: { type: 'string', description: 'Relative vault path — use this as-is in other calls' },
title: { type: 'string' },
excerpt: { type: 'string', description: 'Matched text excerpt (~200 chars)' },
tags: { type: 'array', items: { type: 'string' } },
modified_date: { type: 'string', format: 'date-time' },
},
},
},
},
},
},
},
},
},
'/api/obsidian/note': {
get: {
operationId: 'obsidian_read_note',
summary: 'Read an Obsidian note',
description: 'Retrieve the full markdown content of a specific note by its vault path.',
parameters: [
{ name: 'path', in: 'query', required: true, schema: { type: 'string' }, description: 'Relative vault path (e.g. "CMG Project/SoFi First Principles Readout for CMG and AIO.md")' },
],
responses: {
'200': {
description: 'Full note',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
path: { type: 'string' },
title: { type: 'string' },
content: { type: 'string', description: 'Full markdown content' },
tags: { type: 'array', items: { type: 'string' } },
links: { type: 'array', items: { type: 'string' }, description: 'Internal [[wiki-links]] found in note' },
modified_date: { type: 'string', format: 'date-time' },
},
},
},
},
},
'404': { description: 'Note not found' },
},
},
put: {
operationId: 'obsidian_update_note',
summary: 'Overwrite an Obsidian note',
description: 'Replace the entire content of a note with new markdown. Creates the note if it does not exist. Use this for rewrites or structural edits; use the append endpoint for adding content.',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['path', 'content'],
properties: {
path: { type: 'string', description: 'Relative vault path' },
content: { type: 'string', description: 'Full markdown content — replaces existing content entirely' },
},
},
},
},
},
responses: {
'200': {
description: 'Note written',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
success: { type: 'boolean' },
path: { type: 'string' },
bytes_written: { type: 'integer' },
},
},
},
},
},
},
},
},
'/api/obsidian/note/append': {
post: {
operationId: 'obsidian_append_to_note',
summary: 'Append to an Obsidian note',
description: 'Add markdown content to the end of a note, optionally under an H2 section header. Creates the note if it does not exist.',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['path', 'content'],
properties: {
path: { type: 'string', description: 'Relative vault path (e.g. "Daily Notes/2026-04-28.md")' },
content: { type: 'string', description: 'Markdown content to append' },
header: { type: 'string', description: 'Optional H2 section header inserted before the content' },
create_if_missing: { type: 'boolean', default: true, description: 'Create the note if it does not exist' },
},
},
},
},
},
responses: {
'200': {
description: 'Content appended',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
success: { type: 'boolean' },
path: { type: 'string' },
bytes_written: { type: 'integer' },
},
},
},
},
},
},
},
},
'/api/obsidian/sync': {
get: {
operationId: 'obsidian_sync_status',
summary: 'Check vault sync status',
description: 'Returns Syncthing sync state and vault statistics.',
responses: {
'200': {
description: 'Sync status',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
status: { type: 'string', description: 'Syncthing state (idle, syncing, error, etc.)' },
last_sync: { type: 'string', format: 'date-time', nullable: true },
vault_size: { type: 'integer', description: 'Total vault size in bytes' },
pending_changes: { type: 'integer', description: 'Files awaiting sync' },
},
},
},
},
},
},
},
},
// ── Email ───────────────────────────────────────────────────
'/api/email/profile': {
get: {
operationId: 'get_profile',
summary: 'Get email account profile',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' }, description: 'Mailbox account (yahoo, fetcherpay, garfield, sales, leads, founder, gmail)' },
],
responses: { '200': { description: 'Profile info' } },
},
},
'/api/email/search': {
get: {
operationId: 'search_messages',
summary: 'Search email messages',
parameters: [
{ name: 'q', in: 'query', required: true, schema: { type: 'string' } },
{ name: 'maxResults', in: 'query', schema: { type: 'integer', default: 20 } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
{ name: 'folder', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Search results' } },
},
},
'/api/email/read': {
get: {
operationId: 'read_message',
summary: 'Read email by UID',
parameters: [
{ name: 'uid', in: 'query', required: true, schema: { type: 'integer' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
{ name: 'folder', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Message body' } },
},
},
'/api/email/send': {
post: {
operationId: 'send_email',
summary: 'Send email',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['to', 'subject', 'body'],
properties: {
to: { type: 'string' },
subject: { type: 'string' },
body: { type: 'string' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Email sent' } },
},
},
// ── WhatsApp ────────────────────────────────────────────────
'/api/whatsapp/send': {
post: {
operationId: 'whatsapp_send_message',
summary: 'Send WhatsApp message',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['to', 'message'],
properties: {
to: { type: 'string', description: 'Phone number in international format' },
message: { type: 'string' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Message sent' } },
},
},
'/api/whatsapp/template': {
post: {
operationId: 'whatsapp_send_template',
summary: 'Send WhatsApp template',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['to', 'template_name'],
properties: {
to: { type: 'string' },
template_name: { type: 'string' },
language: { type: 'string' },
components: { type: 'array', items: { type: 'object' } },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Template sent' } },
},
},
'/api/whatsapp/templates': {
get: {
operationId: 'whatsapp_list_templates',
summary: 'List WhatsApp templates',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Template list' } },
},
},
// ── LinkedIn ────────────────────────────────────────────────
'/api/linkedin/profile': {
get: {
operationId: 'linkedin_get_profile',
summary: 'Get LinkedIn profile',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Profile info' } },
},
},
'/api/linkedin/post': {
post: {
operationId: 'linkedin_create_post',
summary: 'Create LinkedIn post',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['text'],
properties: {
text: { type: 'string' },
visibility: { type: 'string', enum: ['PUBLIC', 'CONNECTIONS'] },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Post created' } },
},
},
'/api/linkedin/video': {
post: {
operationId: 'linkedin_upload_video',
summary: 'Upload video and create LinkedIn post',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['video_url', 'text'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the MP4 video' },
text: { type: 'string' },
visibility: { type: 'string', enum: ['PUBLIC', 'CONNECTIONS'] },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Video uploaded and posted' } },
},
},
// ── Telegram ────────────────────────────────────────────────
'/api/telegram/message': {
post: {
operationId: 'telegram_send_message',
summary: 'Send Telegram message',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['chat_id', 'text'],
properties: {
chat_id: { type: 'string' },
text: { type: 'string' },
parse_mode: { type: 'string' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Message sent' } },
},
},
'/api/telegram/updates': {
get: {
operationId: 'telegram_get_updates',
summary: 'Get Telegram updates',
parameters: [
{ name: 'limit', in: 'query', schema: { type: 'integer' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Updates list' } },
},
},
// ── Discord ─────────────────────────────────────────────────
'/api/discord/guilds': {
get: {
operationId: 'discord_get_guilds',
summary: 'List Discord servers',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Guild list' } },
},
},
'/api/discord/message': {
post: {
operationId: 'discord_send_message',
summary: 'Send Discord message',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['channel_id', 'content'],
properties: {
channel_id: { type: 'string' },
content: { type: 'string' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Message sent' } },
},
},
'/api/discord/messages': {
get: {
operationId: 'discord_get_messages',
summary: 'Get Discord messages',
parameters: [
{ name: 'channel_id', in: 'query', required: true, schema: { type: 'string' } },
{ name: 'limit', in: 'query', schema: { type: 'integer' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Message list' } },
},
},
// ── Instagram ───────────────────────────────────────────────
'/api/instagram/profile': {
get: {
operationId: 'instagram_get_profile',
summary: 'Get Instagram profile',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Profile info' } },
},
},
'/api/instagram/media': {
get: {
operationId: 'instagram_get_media',
summary: 'Get Instagram media',
parameters: [
{ name: 'limit', in: 'query', schema: { type: 'integer' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Media list' } },
},
},
'/api/instagram/post': {
post: {
operationId: 'instagram_create_post',
summary: 'Create Instagram post',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['image_url'],
properties: {
image_url: { type: 'string' },
caption: { type: 'string' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Post created' } },
},
},
// ── Facebook ───────────────────────────────────────────────
'/api/facebook/page': {
get: {
operationId: 'facebook_get_page',
summary: 'Get Facebook Page profile',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Page info' } },
},
},
'/api/facebook/posts': {
get: {
operationId: 'facebook_get_posts',
summary: 'Get Facebook Page posts',
parameters: [
{ name: 'limit', in: 'query', schema: { type: 'integer' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Post list' } },
},
},
'/api/facebook/post': {
post: {
operationId: 'facebook_create_post',
summary: 'Publish text post to Facebook Page',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['message'],
properties: {
message: { type: 'string' },
link: { type: 'string' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Post created' } },
},
},
'/api/facebook/photo': {
post: {
operationId: 'facebook_create_photo_post',
summary: 'Publish photo post to Facebook Page',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['image_url'],
properties: {
image_url: { type: 'string' },
caption: { type: 'string' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Photo posted' } },
},
},
'/api/facebook/video': {
post: {
operationId: 'facebook_create_video_post',
summary: 'Publish video post to Facebook Page',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['video_url'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the video' },
description: { type: 'string', description: 'Video description text' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Video posted' } },
},
},
// ── Twitter/X ───────────────────────────────────────────────
'/api/twitter/search': {
get: {
operationId: 'twitter_search_tweets',
summary: 'Search tweets',
parameters: [
{ name: 'query', in: 'query', required: true, schema: { type: 'string' } },
{ name: 'max_results', in: 'query', schema: { type: 'integer' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Tweet list' } },
},
},
'/api/twitter/user': {
get: {
operationId: 'twitter_get_user_profile',
summary: 'Get Twitter user profile',
parameters: [
{ name: 'username', in: 'query', required: true, schema: { type: 'string' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'User profile' } },
},
},
'/api/twitter/tweets': {
get: {
operationId: 'twitter_get_user_tweets',
summary: 'Get user tweets',
parameters: [
{ name: 'username', in: 'query', required: true, schema: { type: 'string' } },
{ name: 'max_results', in: 'query', schema: { type: 'integer' } },
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Tweet list' } },
},
},
'/api/twitter/video': {
post: {
operationId: 'twitter_upload_video',
summary: 'Upload video and post tweet',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['video_url', 'text'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the MP4 video' },
text: { type: 'string', description: 'Tweet text content' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Video tweet posted' } },
},
},
'/api/instagram/reel': {
post: {
operationId: 'instagram_create_reel',
summary: 'Create Instagram Reel',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['video_url'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the MP4 video' },
caption: { type: 'string', description: 'Reel caption text' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Reel created' } },
},
},
'/api/tiktok/profile': {
get: {
operationId: 'tiktok_get_profile',
summary: 'Get TikTok profile',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Profile info' } },
},
},
'/api/tiktok/creator-info': {
get: {
operationId: 'tiktok_get_creator_info',
summary: 'Get TikTok creator info',
parameters: [
{ name: 'account', in: 'query', schema: { type: 'string' } },
],
responses: { '200': { description: 'Creator publishing info' } },
},
},
'/api/tiktok/video': {
post: {
operationId: 'tiktok_create_video',
summary: 'Upload video to TikTok',
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['video_url'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the video' },
title: { type: 'string', description: 'Video title (max 150 chars)' },
description: { type: 'string', description: 'Video description / caption' },
account: { type: 'string' },
},
},
},
},
},
responses: { '200': { description: 'Video upload initiated' } },
},
},
},
};
}
const MAIL_PATHS = new Set([
'/api/obsidian/search',
'/api/obsidian/note',
'/api/obsidian/note/append',
'/api/obsidian/sync',
'/api/email/profile',
'/api/email/search',
'/api/email/read',
'/api/email/send',
]);
const SOCIAL_PATHS = new Set([
'/api/whatsapp/send',
'/api/linkedin/profile',
'/api/linkedin/post',
'/api/linkedin/video',
'/api/telegram/message',
'/api/telegram/updates',
'/api/discord/guilds',
'/api/discord/message',
'/api/discord/messages',
'/api/instagram/profile',
'/api/instagram/media',
'/api/instagram/post',
'/api/instagram/reel',
'/api/facebook/page',
'/api/facebook/posts',
'/api/facebook/post',
'/api/facebook/photo',
'/api/facebook/video',
'/api/twitter/search',
'/api/twitter/user',
'/api/twitter/tweets',
'/api/twitter/video',
'/api/tiktok/profile',
'/api/tiktok/videos',
'/api/tiktok/video',
]);
function filterPaths(fullSpec: Record<string, unknown>, allowed: Set<string>): Record<string, unknown> {
const paths = fullSpec.paths as Record<string, unknown>;
const filtered: Record<string, unknown> = {};
for (const [key, value] of Object.entries(paths)) {
if (allowed.has(key)) filtered[key] = value;
}
return filtered;
}
export function getOpenApiSpecMail(serverUrl: string) {
const full = getOpenApiSpec(serverUrl);
return {
...full,
info: {
...full.info,
title: 'Hermes Mail',
description: 'Email operations across multiple accounts and Obsidian vault access (search, read, append, update notes).',
},
paths: filterPaths(full, MAIL_PATHS),
};
}
export function getOpenApiSpecSocial(serverUrl: string) {
const full = getOpenApiSpec(serverUrl);
return {
...full,
info: {
...full.info,
title: 'Hermes Social',
description: 'Social media publishing and analytics: LinkedIn, TikTok, Instagram, Facebook, Twitter/X, Telegram, WhatsApp, Discord.',
},
paths: filterPaths(full, SOCIAL_PATHS),
};
}
const ACCOUNT_PARAM_SCHEMA = {
account: {
type: 'string',
enum: ['yahoo', 'fetcherpay', 'garfield', 'sales', 'leads', 'founder', 'gmail'],
description:
'Which mailbox to use: "yahoo" (gheron01@yahoo.com), "fetcherpay" (garfield.heron@fetcherpay.com), "garfield" (garfield@fetcherpay.com), "sales" (sales@fetcherpay.com), "leads" (leads@fetcherpay.com), "founder" (founder@fetcherpay.com), or "gmail" (Gmail account). Defaults to "yahoo".',
},
};
export function getManifest(serverUrl: string, authEnabled: boolean) {
return {
schema_version: SCHEMA_VERSION,
oauth_endpoints: {
issuer: serverUrl,
authorization_server_base: serverUrl,
authorization: `${serverUrl}/oauth/authorize`,
token: `${serverUrl}/oauth/token`,
registration: `${serverUrl}/oauth/register`,
},
server: {
name: 'hermes-mcp',
version: '1.0.0',
url: serverUrl,
auth: authEnabled
? {
type: 'apiKey',
header: 'x-api-key',
location: 'header',
note: 'Also accepted as ?key= query parameter',
}
: { type: 'none' },
},
tools: [
// ── Email tools ─────────────────────────────────────────────────────────
{
name: 'get_profile',
category: 'email',
description: 'Get the email account profile (email address and display name)',
when_to_use:
'User asks for their email address, display name, or account identity before sending or reading messages.',
input_schema: {
type: 'object',
properties: { ...ACCOUNT_PARAM_SCHEMA },
},
returns: {
type: 'object',
properties: {
email: { type: 'string', description: 'Full email address' },
name: { type: 'string', description: 'Username portion of the email' },
account: { type: 'string', description: 'Account alias used' },
},
},
examples: [{ account: 'yahoo' }],
},
{
name: 'search_messages',
category: 'email',
description: 'Search email messages by keyword, sender, or subject across the selected account',
when_to_use:
'User asks to find, look up, or search for emails, messages, or correspondence in Yahoo or FetcherPay mailboxes.',
input_schema: {
type: 'object',
required: ['q'],
properties: {
q: {
type: 'string',
description: 'Search query — can be a keyword, from:email, or subject:text',
},
maxResults: {
type: 'number',
description: 'Maximum number of messages to return',
default: 20,
},
...ACCOUNT_PARAM_SCHEMA,
},
},
returns: {
type: 'array',
items: {
type: 'object',
properties: {
uid: { type: 'number' },
messageId: { type: 'string' },
subject: { type: 'string' },
from: { type: 'string' },
date: { type: 'string', format: 'date-time' },
seen: { type: 'boolean' },
size: { type: 'number' },
},
},
},
examples: [{ q: 'invoice', maxResults: 10, account: 'fetcherpay' }],
},
{
name: 'read_message',
category: 'email',
description: 'Read a full email message including body text by its UID',
when_to_use:
'User asks to read, open, show, or summarize a specific email message identified by UID from search results.',
input_schema: {
type: 'object',
required: ['uid'],
properties: {
uid: {
type: 'number',
description: 'Message UID returned by search_messages',
},
...ACCOUNT_PARAM_SCHEMA,
},
},
returns: {
type: 'object',
properties: {
uid: { type: 'number' },
messageId: { type: 'string' },
subject: { type: 'string' },
from: { type: 'string' },
to: { type: 'string' },
date: { type: 'string', format: 'date-time' },
body: { type: 'string', description: 'Plain-text body (HTML stripped)' },
seen: { type: 'boolean' },
},
},
examples: [{ uid: 12345, account: 'yahoo' }],
},
{
name: 'list_folders',
category: 'email',
description: 'List all email folders and mailboxes for the selected account',
when_to_use:
'User asks what folders exist, wants to know mailbox structure, or needs folder names for email organization.',
input_schema: {
type: 'object',
properties: { ...ACCOUNT_PARAM_SCHEMA },
},
returns: {
type: 'array',
items: { type: 'string', description: 'Folder path (e.g. INBOX, Sent)' },
},
examples: [{ account: 'yahoo' }],
},
{
name: 'create_draft',
category: 'email',
description: 'Create a draft email saved to the Drafts folder',
when_to_use:
'User wants to compose or save a draft email without sending it immediately.',
input_schema: {
type: 'object',
required: ['to', 'subject', 'body'],
properties: {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject line' },
body: { type: 'string', description: 'Email body in plain text' },
...ACCOUNT_PARAM_SCHEMA,
},
},
returns: {
type: 'string',
description: 'Confirmation message indicating the draft was created',
},
examples: [
{
to: 'partner@example.com',
subject: 'Meeting follow-up',
body: 'Thanks for the call today.',
account: 'fetcherpay',
},
],
},
{
name: 'send_email',
category: 'email',
description: 'Send an email immediately via SMTP',
when_to_use:
'User asks to send an email, message, or mail to someone right now.',
input_schema: {
type: 'object',
required: ['to', 'subject', 'body'],
properties: {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject line' },
body: { type: 'string', description: 'Email body in plain text' },
...ACCOUNT_PARAM_SCHEMA,
},
},
returns: {
type: 'string',
description: 'SMTP message ID confirming the send',
},
examples: [
{
to: 'team@fetcherpay.com',
subject: 'Weekly update',
body: 'Here is the weekly summary.',
account: 'garfield',
},
],
},
// ── WhatsApp Business API tools ───────────────────────────────────────
{
name: 'whatsapp_send_message',
category: 'whatsapp',
description: 'Send a WhatsApp text message to a phone number',
when_to_use:
'User asks to send a WhatsApp message, text someone on WhatsApp, or notify via WhatsApp. Only works if the recipient has messaged you within the last 24 hours.',
input_schema: {
type: 'object',
required: ['to', 'message'],
properties: {
to: { type: 'string', description: 'Recipient phone number in international format (e.g. +1234567890)' },
message: { type: 'string', description: 'Message text to send' },
account: { type: 'string', description: 'Which WhatsApp account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
message_id: { type: 'string', description: 'WhatsApp message ID' },
},
},
examples: [{ to: '+1234567890', message: 'Hello from Hermes', account: 'default' }],
},
{
name: 'whatsapp_send_template',
category: 'whatsapp',
description: 'Send an approved WhatsApp template message',
when_to_use:
'User wants to send a structured notification or alert via an approved WhatsApp template. Required when outside the 24-hour customer-service window.',
input_schema: {
type: 'object',
required: ['to', 'template_name'],
properties: {
to: { type: 'string', description: 'Recipient phone number in international format' },
template_name: { type: 'string', description: 'Name of the approved WhatsApp template' },
language: { type: 'string', description: 'Template language code (default: "en")' },
components: { type: 'array', items: { type: 'object' }, description: 'Template components (header, body, buttons) with parameters' },
account: { type: 'string', description: 'Which WhatsApp account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
message_id: { type: 'string', description: 'WhatsApp message ID' },
},
},
examples: [{ to: '+1234567890', template_name: 'hello_world', language: 'en', account: 'default' }],
},
{
name: 'whatsapp_list_templates',
category: 'whatsapp',
description: 'List all approved WhatsApp message templates for the business account',
when_to_use:
'User asks what WhatsApp templates are available or wants to know which templates can be sent.',
input_schema: {
type: 'object',
properties: {
account: { type: 'string', description: 'Which WhatsApp account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
templates: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
language: { type: 'string' },
status: { type: 'string' },
},
},
},
},
},
examples: [{ account: 'default' }],
},
// ── LinkedIn tools ──────────────────────────────────────────────────────
{
name: 'linkedin_get_profile',
category: 'linkedin',
description: 'Get the LinkedIn profile of the authenticated user',
when_to_use:
'User asks about their LinkedIn profile, name, headline, or wants to verify which LinkedIn account is connected.',
input_schema: {
type: 'object',
properties: {
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
id: { type: 'string', description: 'LinkedIn person ID (OpenID sub)' },
firstName: { type: 'string' },
lastName: { type: 'string' },
email: { type: 'string' },
picture: { type: 'string', description: 'Profile photo URL' },
},
},
examples: [{ account: 'default' }],
},
{
name: 'linkedin_create_post',
category: 'linkedin',
description: 'Create a post on LinkedIn',
when_to_use:
'User wants to publish an update, share content, or post to their LinkedIn feed.',
input_schema: {
type: 'object',
required: ['text'],
properties: {
text: { type: 'string', description: 'Post content text' },
visibility: { type: 'string', enum: ['PUBLIC', 'CONNECTIONS'], description: 'PUBLIC (anyone) or CONNECTIONS (1st degree only)' },
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
post_id: { type: 'string' },
url: { type: 'string', description: 'Direct link to the post' },
},
},
examples: [{ text: 'Excited to share our latest product update!', visibility: 'PUBLIC', account: 'default' }],
},
{
name: 'linkedin_search_connections',
category: 'linkedin',
description: 'Search LinkedIn connections [REQUIRES PARTNERSHIP]',
when_to_use:
'User wants to search their LinkedIn network. NOTE: Requires LinkedIn Partnership Program — public API access was removed.',
input_schema: {
type: 'object',
properties: {
keywords: { type: 'string', description: 'Search keywords' },
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
message: { type: 'string', description: 'Error or guidance message' },
},
},
examples: [{ keywords: 'software engineer', account: 'default' }],
},
{
name: 'linkedin_send_message',
category: 'linkedin',
description: 'Send a direct message on LinkedIn [REQUIRES PARTNERSHIP]',
when_to_use:
'User wants to send a LinkedIn DM. NOTE: Requires LinkedIn Partnership Program — messaging is not available through the public API.',
input_schema: {
type: 'object',
required: ['recipient_id', 'message'],
properties: {
recipient_id: { type: 'string', description: 'LinkedIn person URN or ID' },
message: { type: 'string', description: 'Message text' },
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
message: { type: 'string', description: 'Error or guidance message' },
},
},
examples: [{ recipient_id: 'urn:li:person:abc123', message: 'Hi, thanks for connecting!', account: 'default' }],
},
// ── Telegram tools ──────────────────────────────────────────────────────
{
name: 'telegram_get_me',
category: 'telegram',
description: 'Get information about the connected Telegram bot',
when_to_use:
'User asks about the Telegram bot, wants to verify it is connected, or needs the bot username.',
input_schema: {
type: 'object',
properties: {
account: { type: 'string', description: 'Which Telegram account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
id: { type: 'number', description: 'Bot user ID' },
first_name: { type: 'string' },
username: { type: 'string' },
is_bot: { type: 'boolean' },
},
},
examples: [{ account: 'default' }],
},
{
name: 'telegram_send_message',
category: 'telegram',
description: 'Send a text message via Telegram bot',
when_to_use:
'User wants to send a Telegram message, DM someone, or notify a group or channel.',
input_schema: {
type: 'object',
required: ['chat_id', 'text'],
properties: {
chat_id: { type: 'string', description: 'Chat ID, username (@username), or channel ID' },
text: { type: 'string', description: 'Message text' },
parse_mode: { type: 'string', enum: ['HTML', 'Markdown', 'MarkdownV2'], description: 'Message formatting mode' },
account: { type: 'string', description: 'Which Telegram account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
message_id: { type: 'number' },
chat_id: { type: 'string', description: 'Chat ID where the message was sent' },
},
},
examples: [{ chat_id: '@mychannel', text: 'Hello from Hermes!', account: 'default' }],
},
{
name: 'telegram_send_photo',
category: 'telegram',
description: 'Send a photo via Telegram bot',
when_to_use:
'User wants to share an image through Telegram.',
input_schema: {
type: 'object',
required: ['chat_id', 'photo'],
properties: {
chat_id: { type: 'string', description: 'Chat ID, username (@username), or channel ID' },
photo: { type: 'string', description: 'Photo URL or Telegram file_id' },
caption: { type: 'string', description: 'Optional caption text' },
account: { type: 'string', description: 'Which Telegram account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
message_id: { type: 'number' },
chat_id: { type: 'string' },
},
},
examples: [{ chat_id: '@mychannel', photo: 'https://example.com/image.jpg', caption: 'Check this out', account: 'default' }],
},
{
name: 'telegram_get_updates',
category: 'telegram',
description: 'Get recent incoming messages for the Telegram bot',
when_to_use:
'User asks to check Telegram messages, read DMs, or see recent bot activity.',
input_schema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Max updates to return (default: 10)' },
account: { type: 'string', description: 'Which Telegram account to use (default: "default")' },
},
},
returns: {
type: 'array',
items: {
type: 'object',
properties: {
update_id: { type: 'number' },
message: {
type: 'object',
properties: {
message_id: { type: 'number' },
from: { type: 'object', properties: { id: { type: 'number' }, first_name: { type: 'string' }, username: { type: 'string' } } },
chat: { type: 'object', properties: { id: { type: 'number' }, type: { type: 'string' }, title: { type: 'string' } } },
date: { type: 'number' },
text: { type: 'string' },
},
},
},
},
},
examples: [{ limit: 5, account: 'default' }],
},
{
name: 'telegram_get_chat',
category: 'telegram',
description: 'Get information about a Telegram chat or channel',
when_to_use:
'User wants to verify a Telegram chat exists or get its details before sending a message.',
input_schema: {
type: 'object',
required: ['chat_id'],
properties: {
chat_id: { type: 'string', description: 'Chat ID, username (@username), or channel ID' },
account: { type: 'string', description: 'Which Telegram account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
id: { type: 'number' },
type: { type: 'string' },
title: { type: 'string' },
username: { type: 'string' },
description: { type: 'string' },
member_count: { type: 'number' },
},
},
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' }],
},
{
name: 'instagram_create_reel',
category: 'instagram',
description: 'Upload a video as an Instagram Reel',
when_to_use:
'User wants to publish a video to their Instagram Business/Creator account as a Reel.',
input_schema: {
type: 'object',
required: ['video_url'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the MP4 video' },
caption: { type: 'string', description: 'Reel caption' },
account: { type: 'string', description: 'Which Instagram account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
media_id: { type: 'string' },
},
},
examples: [{ video_url: 'https://example.com/video.mp4', caption: 'New reel!', account: 'default' }],
},
// ── Facebook tools ─────────────────────────────────────────────────────
{
name: 'facebook_get_page',
category: 'facebook',
description: 'Get a Facebook Page profile including name, category, fan count, and follower count',
when_to_use: 'User asks about their Facebook Page stats, followers, or account details.',
input_schema: {
type: 'object',
properties: {
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
category: { type: 'string' },
about: { type: 'string' },
fan_count: { type: 'number' },
followers_count: { type: 'number' },
link: { type: 'string' },
},
},
examples: [{ account: 'default' }],
},
{
name: 'facebook_get_posts',
category: 'facebook',
description: 'Get recent posts from a Facebook Page feed',
when_to_use: 'User wants to see recent Facebook Page posts or content history.',
input_schema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Max posts (default: 10)' },
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
},
},
returns: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
message: { type: 'string' },
story: { type: 'string' },
created_time: { type: 'string' },
permalink_url: { type: 'string' },
},
},
},
examples: [{ limit: 5, account: 'default' }],
},
{
name: 'facebook_create_post',
category: 'facebook',
description: 'Publish a text post (optionally with a link) to a Facebook Page',
when_to_use: 'User wants to post a status update or share a link on their Facebook Page.',
input_schema: {
type: 'object',
required: ['message'],
properties: {
message: { type: 'string', description: 'Post text content' },
link: { type: 'string', description: 'Optional URL to attach' },
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
post_id: { type: 'string' },
},
},
examples: [{ message: 'Something new is coming next week.', account: 'default' }],
},
{
name: 'facebook_create_photo_post',
category: 'facebook',
description: 'Publish a photo post to a Facebook Page using a public image URL',
when_to_use: 'User wants to post an image or photo to their Facebook Page.',
input_schema: {
type: 'object',
required: ['image_url'],
properties: {
image_url: { type: 'string', description: 'Publicly accessible URL of the image' },
caption: { type: 'string', description: 'Post caption text' },
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
post_id: { type: 'string' },
photo_id: { type: 'string' },
},
},
examples: [{ image_url: 'https://example.com/image.jpg', caption: 'New post!', account: 'default' }],
},
{
name: 'facebook_create_video_post',
category: 'facebook',
description: 'Publish a video post to a Facebook Page using a public video URL',
when_to_use: 'User wants to post a video to their Facebook Page.',
input_schema: {
type: 'object',
required: ['video_url'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the video' },
description: { type: 'string', description: 'Video description text' },
account: { type: 'string', description: 'Which Facebook account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
video_id: { type: 'string' },
post_id: { type: 'string' },
},
},
examples: [{ video_url: 'https://example.com/video.mp4', description: 'New video!', account: 'default' }],
},
// ── Twitter/X tools ────────────────────────────────────────────────────
{
name: 'twitter_search_tweets',
category: 'twitter',
description: 'Search recent tweets on Twitter/X',
when_to_use:
'User wants to find tweets about a topic, hashtag, keyword, or trend.',
input_schema: {
type: 'object',
required: ['query'],
properties: {
query: { type: 'string', description: 'Search query (keyword, hashtag #xxx, from:username)' },
max_results: { type: 'number', description: 'Max tweets (default: 10, max: 100)' },
account: { type: 'string', description: 'Which Twitter account to use (default: "default")' },
},
},
returns: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Tweet ID' },
text: { type: 'string' },
author_id: { type: 'string' },
created_at: { type: 'string', format: 'date-time' },
},
},
},
examples: [{ query: '#AI', max_results: 10, account: 'default' }],
},
{
name: 'twitter_get_user_profile',
category: 'twitter',
description: 'Get a Twitter/X user profile and stats',
when_to_use:
'User wants follower count, bio, or profile info for a specific Twitter account.',
input_schema: {
type: 'object',
required: ['username'],
properties: {
username: { type: 'string', description: 'Twitter username without @' },
account: { type: 'string', description: 'Which Twitter account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
username: { type: 'string' },
description: { type: 'string' },
followers_count: { type: 'number' },
following_count: { type: 'number' },
tweet_count: { type: 'number' },
},
},
examples: [{ username: 'elonmusk', account: 'default' }],
},
{
name: 'twitter_get_user_tweets',
category: 'twitter',
description: 'Get recent tweets from a specific user',
when_to_use:
'User wants to read someones recent tweets or timeline.',
input_schema: {
type: 'object',
required: ['username'],
properties: {
username: { type: 'string', description: 'Twitter username without @' },
max_results: { type: 'number', description: 'Max tweets (default: 10, max: 100)' },
account: { type: 'string', description: 'Which Twitter account to use (default: "default")' },
},
},
returns: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
text: { type: 'string' },
created_at: { type: 'string', format: 'date-time' },
},
},
},
examples: [{ username: 'elonmusk', max_results: 5, account: 'default' }],
},
{
name: 'twitter_create_tweet',
category: 'twitter',
description: 'Post a tweet [REQUIRES PAID TIER]',
when_to_use:
'User wants to post a tweet. NOTE: Free tier is read-only. Paid upgrade required.',
input_schema: {
type: 'object',
required: ['text'],
properties: {
text: { type: 'string', description: 'Tweet text (max 280 chars)' },
account: { type: 'string', description: 'Which Twitter account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
message: { type: 'string', description: 'Error or confirmation' },
},
},
examples: [{ text: 'Hello from Hermes MCP!', account: 'default' }],
},
{
name: 'twitter_upload_video',
category: 'twitter',
description: 'Upload a video and post it as a tweet on Twitter/X',
when_to_use:
'User wants to post a video to Twitter/X. NOTE: Free tier is read-only. Paid upgrade required.',
input_schema: {
type: 'object',
required: ['video_url', 'text'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the MP4 video' },
text: { type: 'string', description: 'Tweet text content' },
account: { type: 'string', description: 'Which Twitter account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
tweet_id: { type: 'string' },
url: { type: 'string', description: 'Direct link to the tweet' },
},
},
examples: [{ video_url: 'https://example.com/video.mp4', text: 'Check this out!', account: 'default' }],
},
{
name: 'tiktok_get_profile',
category: 'tiktok',
description: 'Get the TikTok user profile including follower count, following count, likes, and video count',
when_to_use: 'User asks about their TikTok profile stats or account details.',
input_schema: {
type: 'object',
properties: {
account: { type: 'string', description: 'Which TikTok account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
open_id: { type: 'string' },
display_name: { type: 'string' },
follower_count: { type: 'number' },
following_count: { type: 'number' },
likes_count: { type: 'number' },
video_count: { type: 'number' },
},
},
examples: [{ account: 'default' }],
},
{
name: 'tiktok_get_creator_info',
category: 'tiktok',
description: 'Get TikTok creator publishing info including privacy levels and max video duration',
when_to_use: 'User wants to check their TikTok publishing capabilities before posting a video.',
input_schema: {
type: 'object',
properties: {
account: { type: 'string', description: 'Which TikTok account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
creator_username: { type: 'string' },
creator_nickname: { type: 'string' },
creator_avatar_url: { type: 'string' },
privacy_level_options: { type: 'array', items: { type: 'string' } },
max_video_post_duration_sec: { type: 'number' },
comment_disabled: { type: 'boolean' },
duet_disabled: { type: 'boolean' },
stitch_disabled: { type: 'boolean' },
},
},
examples: [{ account: 'default' }],
},
{
name: 'tiktok_create_video',
category: 'tiktok',
description: 'Post a video to TikTok by providing a publicly accessible video URL',
when_to_use: 'User wants to upload a video to TikTok.',
input_schema: {
type: 'object',
required: ['video_url'],
properties: {
video_url: { type: 'string', description: 'Publicly accessible URL of the video to post' },
title: { type: 'string', description: 'Video title (max 150 chars)' },
description: { type: 'string', description: 'Video description / caption' },
account: { type: 'string', description: 'Which TikTok account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
publish_id: { type: 'string' },
status: { type: 'string' },
},
},
examples: [{ video_url: 'https://example.com/video.mp4', title: 'My video', account: 'default' }],
},
{
name: 'tiktok_get_video_status',
category: 'tiktok',
description: 'Check the processing status of a TikTok video upload',
when_to_use: 'User wants to check if their TikTok video upload is complete.',
input_schema: {
type: 'object',
required: ['publish_id'],
properties: {
publish_id: { type: 'string', description: 'Publish ID returned by tiktok_create_video' },
account: { type: 'string', description: 'Which TikTok account to use (default: "default")' },
},
},
returns: {
type: 'object',
properties: {
publish_id: { type: 'string' },
status: { type: 'string' },
fail_reason: { type: 'string' },
},
},
examples: [{ publish_id: 'v123456', account: 'default' }],
},
// ── Obsidian tools ──────────────────────────────────────────────────────
{
name: 'obsidian_search_notes',
category: 'obsidian',
description: 'Full-text search across the Obsidian vault by content, tags, or title',
when_to_use:
'User mentions "my notes", "in obsidian", "I wrote about", asks about personal knowledge, or references past notes.',
input_schema: {
type: 'object',
required: ['query'],
properties: {
query: {
type: 'string',
description: 'Text to search for in note content or title',
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Filter by Obsidian tags (all must match)',
},
limit: {
type: 'number',
description: 'Max results to return',
default: 10,
},
path_filter: {
type: 'string',
description: 'Only return notes whose path contains this string (e.g. "Daily Notes")',
},
},
},
returns: {
type: 'array',
items: {
type: 'object',
properties: {
path: { type: 'string', description: 'Relative file path in vault' },
title: { type: 'string' },
excerpt: { type: 'string', description: 'Matched text excerpt (~200 chars)' },
tags: { type: 'array', items: { type: 'string' } },
modified_date: { type: 'string', format: 'date-time' },
},
},
},
examples: [{ query: 'funding strategy', limit: 5 }],
},
{
name: 'obsidian_read_note',
category: 'obsidian',
description: 'Retrieve the complete content of a specific Obsidian note by path or title',
when_to_use:
'User asks to read, open, show, or see a specific note by path or title.',
input_schema: {
type: 'object',
required: ['path'],
properties: {
path: {
type: 'string',
description: 'File path relative to vault root (e.g. "Daily Notes/2026-04-15.md") or just the note title/filename',
},
},
},
returns: {
type: 'object',
properties: {
path: { type: 'string' },
title: { type: 'string' },
content: { type: 'string', description: 'Full markdown content' },
tags: { type: 'array', items: { type: 'string' } },
links: {
type: 'array',
items: { type: 'string' },
description: 'Internal wiki-style links extracted from content',
},
modified_date: { type: 'string', format: 'date-time' },
},
},
examples: [{ path: 'Projects/FetcherPay.md' }],
},
{
name: 'obsidian_append_to_note',
category: 'obsidian',
description: 'Append markdown content to any Obsidian note, creating it if missing',
when_to_use:
'User wants to log, save, journal, or add content to daily notes or specific vault files.',
input_schema: {
type: 'object',
required: ['path', 'content'],
properties: {
path: {
type: 'string',
description: 'File path relative to vault root (e.g. "Daily Notes/2026-04-15.md")',
},
content: {
type: 'string',
description: 'Markdown content to append',
},
create_if_missing: {
type: 'boolean',
description: 'Create the note if it does not exist',
default: true,
},
header: {
type: 'string',
description: 'Optional H2 section header to insert before the content',
},
},
},
returns: {
type: 'object',
properties: {
success: { type: 'boolean' },
path: { type: 'string' },
bytes_written: { type: 'number' },
},
},
examples: [
{
path: 'Daily Notes/2026-04-15.md',
content: '- Met with investor at 2pm',
header: 'Meetings',
},
],
},
{
name: 'obsidian_sync_status',
category: 'obsidian',
description: 'Check Syncthing sync status and vault statistics',
when_to_use:
'User asks if their vault is synced, up to date, or about device connectivity for Obsidian notes.',
input_schema: {
type: 'object',
properties: {},
},
returns: {
type: 'object',
properties: {
status: { type: 'string', description: 'Syncthing state (e.g. idle, syncing, error)' },
last_sync: {
type: ['string', 'null'],
format: 'date-time',
description: 'ISO timestamp of last state change',
},
vault_size: { type: 'number', description: 'Total bytes in vault' },
pending_changes: { type: 'number', description: 'Files needing sync' },
},
},
examples: [{}],
},
],
categories: {
obsidian: {
description: 'Personal knowledge management via Obsidian vault',
icon: '📝',
},
email: {
description: 'Email operations for Yahoo, FetcherPay, and Gmail accounts',
icon: '📧',
},
whatsapp: {
description: 'WhatsApp Business API messaging via Meta Cloud API',
icon: '💬',
},
linkedin: {
description: 'LinkedIn profile and posting via LinkedIn API',
icon: '🔗',
},
telegram: {
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: '📸',
},
twitter: {
description: 'Twitter/X search and profile lookup (read-only on free tier)',
icon: '🐦',
},
facebook: {
description: 'Facebook Page posting and management via Graph API',
icon: '📘',
},
},
};
}