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, allowed: Set): Record { const paths = fullSpec.paths as Record; const filtered: Record = {}; 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: '📘', }, }, }; }