feat: WhatsApp + LinkedIn integrations, SquareMCP rebrand, opencode docs
WhatsApp Business API (Meta Cloud API)
- New client: src/clients/whatsapp.ts
- Tools: whatsapp_send_message, whatsapp_send_template, whatsapp_list_templates
- REST endpoints: POST /api/whatsapp/send, POST /api/whatsapp/template, GET /api/whatsapp/templates
- Multi-account env var pattern: WHATSAPP_{ACCOUNT}_*
LinkedIn API (OpenID Connect)
- New client: src/clients/linkedin.ts
- Tools: linkedin_get_profile, linkedin_create_post, linkedin_search_connections, linkedin_send_message
- REST endpoints: GET /api/linkedin/profile, POST /api/linkedin/post, POST /api/linkedin/search-connections, POST /api/linkedin/message
- Multi-account env var pattern: LINKEDIN_{ACCOUNT}_*
- Uses /v2/userinfo (OpenID Connect) for profile reads
Domain migration
- hermes.fetcherpay.com -> hermes.squaremcp.com
- Updated K8s ingress, TLS cert, SERVER_URL env var
- Updated OPENCODE.md and opencode.json references
SquareMCP site
- Added logo assets (SVG, LinkedIn variants)
- Added terms.html
- Updated Dockerfile, nginx config, styles, index, privacy pages
Docs
- Added OPENCODE.md for opencode AI integration setup
- Updated .env.example with WhatsApp and LinkedIn credentials
- Added opencode.json to .gitignore (contains live API key)
Total tools: 19 (email 6, obsidian 5, whatsapp 4, linkedin 4)
This commit is contained in:
172
src/tools.ts
172
src/tools.ts
@@ -2,6 +2,8 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { searchMessages, readMessage, getProfile, listFolders, type Account } from './imap.js';
|
||||
import { sendEmail, createDraft } from './smtp.js';
|
||||
import { searchNotes, getNote, appendToNote, updateNote, getSyncStatus } from './clients/obsidian.js';
|
||||
import { sendMessage, sendTemplate, getMessageStatus, listTemplates } from './clients/whatsapp.js';
|
||||
import { getProfile as getLinkedInProfile, createPost as createLinkedInPost, searchConnections, sendMessage as sendLinkedInMessage } from './clients/linkedin.js';
|
||||
|
||||
const ACCOUNT_PARAM = {
|
||||
account: {
|
||||
@@ -174,6 +176,114 @@ export const tools: Tool[] = [
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
|
||||
// ── WhatsApp Business API tools ────────────────────────────────
|
||||
{
|
||||
name: 'whatsapp_send_message',
|
||||
description:
|
||||
'Send a WhatsApp message to a phone number. Use when the user asks to send a WhatsApp message, text someone on WhatsApp, or notify via WhatsApp.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
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")' },
|
||||
},
|
||||
required: ['to', 'message'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'whatsapp_send_template',
|
||||
description:
|
||||
'Send a WhatsApp template message (for approved templates). Use when sending structured notifications or alerts via WhatsApp templates.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
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', description: 'Template components (header, body, buttons) with parameters' },
|
||||
account: { type: 'string', description: 'Which WhatsApp account to use (default: "default")' },
|
||||
},
|
||||
required: ['to', 'template_name'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'whatsapp_get_message_status',
|
||||
description:
|
||||
'[DEPRECATED] Meta Cloud API does not support polling message status. Status updates are only available via webhooks.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message_id: { type: 'string', description: 'WhatsApp message ID (not used - webhook required)' },
|
||||
account: { type: 'string', description: 'Which WhatsApp account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'whatsapp_list_templates',
|
||||
description:
|
||||
'List all approved WhatsApp message templates for the business account. Use when the user asks what templates are available.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which WhatsApp account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ── LinkedIn tools ─────────────────────────────────────────────
|
||||
{
|
||||
name: 'linkedin_get_profile',
|
||||
description:
|
||||
'Get the LinkedIn profile of the authenticated user. Use when the user asks about their LinkedIn profile, name, or headline.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkedin_create_post',
|
||||
description:
|
||||
'Create a post on LinkedIn. Use when the user wants to publish an update, article, or share content on LinkedIn.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
text: { type: 'string', description: 'Post content text' },
|
||||
visibility: { type: 'string', enum: ['PUBLIC', 'CONNECTIONS'], description: 'Visibility: PUBLIC (anyone) or CONNECTIONS (1st degree only). Default: PUBLIC' },
|
||||
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
|
||||
},
|
||||
required: ['text'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkedin_search_connections',
|
||||
description:
|
||||
'Search LinkedIn connections. [REQUIRES PARTNERSHIP] LinkedIn removed public API access to connections. This tool will guide you to apply for the Partnership Program.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
keywords: { type: 'string', description: 'Search keywords for connections' },
|
||||
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkedin_send_message',
|
||||
description:
|
||||
'Send a direct message on LinkedIn. [REQUIRES PARTNERSHIP] LinkedIn messaging is not available through the public API. This tool will guide you to apply for the Partnership Program.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
recipient_id: { type: 'string', description: 'LinkedIn person URN or ID of the recipient' },
|
||||
message: { type: 'string', description: 'Message text to send' },
|
||||
account: { type: 'string', description: 'Which LinkedIn account to use (default: "default")' },
|
||||
},
|
||||
required: ['recipient_id', 'message'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function acct(args: Record<string, unknown>): Account {
|
||||
@@ -245,6 +355,68 @@ export async function handleToolCall(
|
||||
result = await getSyncStatus();
|
||||
break;
|
||||
|
||||
// ── WhatsApp Business API ──────────────────────────────────
|
||||
case 'whatsapp_send_message':
|
||||
result = await sendMessage({
|
||||
to: args.to as string,
|
||||
message: args.message as string,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'whatsapp_send_template':
|
||||
result = await sendTemplate({
|
||||
to: args.to as string,
|
||||
template_name: args.template_name as string,
|
||||
language: args.language as string | undefined,
|
||||
components: args.components as unknown[] | undefined,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'whatsapp_get_message_status':
|
||||
result = await getMessageStatus({
|
||||
message_id: args.message_id as string,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'whatsapp_list_templates':
|
||||
result = await listTemplates({
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
// ── LinkedIn ───────────────────────────────────────────────
|
||||
case 'linkedin_get_profile':
|
||||
result = await getLinkedInProfile({
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'linkedin_create_post':
|
||||
result = await createLinkedInPost({
|
||||
text: args.text as string,
|
||||
visibility: (args.visibility as 'PUBLIC' | 'CONNECTIONS') ?? 'PUBLIC',
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'linkedin_search_connections':
|
||||
result = await searchConnections({
|
||||
keywords: args.keywords as string | undefined,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'linkedin_send_message':
|
||||
result = await sendLinkedInMessage({
|
||||
recipient_id: args.recipient_id as string,
|
||||
message: args.message as string,
|
||||
account: args.account as string | undefined,
|
||||
});
|
||||
break;
|
||||
|
||||
// Legacy Yahoo-prefixed names — keep working for any cached Claude sessions
|
||||
case 'yahoo_get_profile':
|
||||
result = await getProfile('yahoo');
|
||||
|
||||
Reference in New Issue
Block a user