Files
hermes-mcp/src/chat.ts
Garfield 18b838c268 feat: ChatGPT Custom GPT support in chat bot + sqcp SMTP routing fix
- chat.ts: system prompt now includes step-by-step ChatGPT Custom GPT
  setup (openapi.json import + OAuth), Claude/Cursor/Windsurf config,
  and mortgage broker guidance — bot no longer incorrectly says ChatGPT
  is unsupported
- smtp.ts: all sqcp_* accounts now route to mail.squaremcp.com (SQCP_SMTP_HOST)
  instead of the fetcherpay server
- tools.ts: ACCOUNT_PARAM description now lists all 14 mailboxes including
  the 7 squaremcp.com accounts so Claude picks the right one without guessing
- package.json: postinstall hook runs imapflow patch script after npm install
- hermes-k8s.yaml: updated image digest to current production build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:53:42 -04:00

162 lines
6.0 KiB
TypeScript

import Anthropic from '@anthropic-ai/sdk';
import { tools as allTools, handleToolCall } from './tools.js';
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const SYSTEM_PROMPT = `You are a live demo assistant for SquareMCP — an AI Social Media Gateway that lets AI agents post to social platforms via the Model Context Protocol (MCP).
You have real tools connected to live platforms. When a visitor asks "can it send a Slack message?" or "show me how it works" — actually do it. Send a demo message, read channels, show real results.
What SquareMCP does:
- Connects AI coding assistants to social platforms: LinkedIn, TikTok, WhatsApp, Instagram, Twitter/X, Facebook, Telegram, Discord, Slack, and Email
- Works with any MCP-compatible client: Claude Desktop, Claude Code, Cursor, Windsurf, opencode, Codex CLI
- Also works with ChatGPT via Custom GPT (uses the REST API + OAuth, not MCP protocol directly)
- Provides a multi-tenant SaaS platform where each customer securely stores their own platform credentials
- Plans: Free (100 calls/month), Pro, Business
How to connect — by AI client:
CHATGPT (chatgpt.com):
SquareMCP works with ChatGPT via a Custom GPT with Actions. Steps:
1. Go to chatgpt.com → Explore GPTs → Create → Actions → Import from URL
2. Import the OpenAPI schema: https://hermes.squaremcp.com/openapi.json
3. Set Authentication to OAuth:
- Authorization URL: https://hermes.squaremcp.com/oauth/authorize
- Token URL: https://hermes.squaremcp.com/oauth/token
- Register your OAuth client first at: https://hermes.squaremcp.com/oauth/register
4. Connect your social accounts in the SquareMCP dashboard at https://app.squaremcp.com
5. Ask your Custom GPT: "Send a WhatsApp to my top 3 clients" — it just works.
Full guide: email support@squaremcp.com for the ChatGPT setup walkthrough.
CLAUDE (claude.ai or Claude Desktop):
1. In claude.ai → Settings → Integrations → Add MCP server
2. Server URL: https://hermes.squaremcp.com/mcp
3. Authenticate via OAuth
4. Connect social accounts at https://app.squaremcp.com
CURSOR / WINDSURF / CODEX CLI / opencode:
Add to your MCP config:
{
"mcpServers": {
"squaremcp": {
"url": "https://hermes.squaremcp.com/mcp",
"apiKey": "your-api-key-from-dashboard"
}
}
}
For mortgage brokers specifically:
- ChatGPT Custom GPT is often the easiest starting point — no install required, works in the browser
- WhatsApp rate blast: one command sends updates to hundreds of clients
- Multi-channel: handle WhatsApp, email, LinkedIn, and Facebook from a single AI conversation
- Compliance: we can scope the setup to stay within RESPA/TILA guidelines
When using tools:
- For demo sends (Slack, Telegram, Discord), send a short, friendly message that explains this is a live SquareMCP demo
- After a tool succeeds, explain what just happened and how easy it was
- If a tool fails (missing credentials), explain what credential the customer would set up instead
- Never expose tokens or internal IDs in your reply
Keep replies concise and practical. If you don't know something, say so and suggest emailing support@squaremcp.com.`;
// Tools the public demo agent is allowed to use (no customer auth needed — uses env var creds)
const DEMO_TOOL_NAMES = new Set([
'slack_get_me',
'slack_get_channels',
'slack_send_message',
'slack_get_messages',
'discord_get_me',
'discord_get_guilds',
'discord_send_message',
'telegram_get_me',
'telegram_send_message',
]);
const demoTools = allTools
.filter(t => DEMO_TOOL_NAMES.has(t.name))
.map(t => ({
name: t.name,
description: t.description,
input_schema: t.inputSchema as Anthropic.Tool['input_schema'],
}));
export interface ChatMessage {
role: 'user' | 'assistant';
content: string;
}
export interface ChatResult {
reply: string;
toolsUsed: string[];
}
export async function handleChat(messages: ChatMessage[]): Promise<ChatResult> {
if (!process.env.ANTHROPIC_API_KEY) {
throw new Error('ANTHROPIC_API_KEY not configured');
}
const apiMessages: Anthropic.MessageParam[] = messages.map(m => ({
role: m.role,
content: m.content,
}));
const toolsUsed: string[] = [];
const MAX_ITERATIONS = 5;
for (let i = 0; i < MAX_ITERATIONS; i++) {
const response = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 1024,
system: SYSTEM_PROMPT,
tools: demoTools,
messages: apiMessages,
});
if (response.stop_reason === 'end_turn') {
const text = response.content.find(b => b.type === 'text');
return { reply: text?.text ?? 'Done.', toolsUsed };
}
if (response.stop_reason === 'tool_use') {
// Add Claude's response (with tool_use blocks) to the message history
apiMessages.push({ role: 'assistant', content: response.content });
// Execute each tool call and collect results
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type !== 'tool_use') continue;
toolsUsed.push(block.name);
try {
const result = await handleToolCall(
block.name,
block.input as Record<string, unknown>,
undefined // no customer — uses env var credentials
);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: result.content[0]?.text ?? '',
is_error: result.isError,
});
} catch (err) {
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: `Error: ${(err as Error).message}`,
is_error: true,
});
}
}
apiMessages.push({ role: 'user', content: toolResults });
continue;
}
// Unexpected stop reason — return whatever text we have
const text = response.content.find(b => b.type === 'text');
return { reply: text?.text ?? 'Something unexpected happened.', toolsUsed };
}
return { reply: 'I ran too many steps. Please try a simpler request.', toolsUsed };
}