- 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>
162 lines
6.0 KiB
TypeScript
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 };
|
|
}
|