feat(chat): upgrade to full agentic demo bot (Option B)

Chat widget now runs a live tool-use loop via Claude Haiku. Exposes
slack, discord, and telegram demo tools — bot can actually send messages
and read channels to prove the platform works in real time. Widget shows
a purple pill with tool names when the agent calls a live platform.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Garfield
2026-05-15 10:49:18 -04:00
parent 4bf93d6763
commit be1a14f783
3 changed files with 111 additions and 26 deletions

View File

@@ -50,6 +50,11 @@
background: ${BRAND}; color: #fff; border-bottom-right-radius: 4px; align-self: flex-end;
}
.smcp-msg.typing { color: #888; font-style: italic; }
.smcp-tools-used {
font-size: 11px; color: #6c47ff; background: #f1f0fe;
border-radius: 6px; padding: 4px 8px; align-self: flex-start;
display: flex; align-items: center; gap: 5px;
}
#smcp-input-row {
padding: 10px 12px; border-top: 1px solid #eee;
display: flex; gap: 8px; flex-shrink: 0;
@@ -152,6 +157,12 @@
indicator.textContent = reply;
indicator.classList.remove('typing');
history.push({ role: 'assistant', content: reply });
if (data.toolsUsed && data.toolsUsed.length > 0) {
const pill = document.createElement('div');
pill.className = 'smcp-tools-used';
pill.textContent = '⚡ Live: ' + data.toolsUsed.join(', ');
messages.appendChild(pill);
}
} catch {
indicator.textContent = 'Network error. Please try again.';
indicator.classList.remove('typing');

View File

@@ -1,49 +1,123 @@
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 friendly and knowledgeable support assistant for SquareMCP — an AI Social Media Gateway that lets AI agents (Claude, ChatGPT, Codex CLI, etc.) post to social platforms via the Model Context Protocol (MCP).
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
- Provides a multi-tenant SaaS platform where each customer securely stores their own platform credentials
- Offers a simple dashboard to connect platforms, view usage, and manage webhooks
- Plans: Free (100 calls/month), Pro, Business
How it works:
1. Customer signs up at the SquareMCP dashboard
2. Connects their social accounts (bot tokens, API keys)
3. Adds the SquareMCP MCP server to their AI client config
4. Their AI agent can now send messages, post content, read analytics — on any connected platform
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
Common questions:
- Pricing: Free tier available, paid plans for higher usage
- Supported platforms: LinkedIn, TikTok, WhatsApp Business, Instagram, Twitter/X, Facebook, Telegram, Discord, Slack, Email
- No coding required to use the dashboard; coding experience helps to get the most from MCP tool calls
- Webhooks: customers can receive real-time events when messages arrive
- Security: credentials are encrypted at rest, each customer's data is isolated
Keep replies concise. If you don't know something, say so and suggest emailing support@squaremcp.com.`;
Keep answers concise (2-4 sentences max). If you don't know something, say so and suggest emailing support@squaremcp.com. Never make up features or pricing. Speak in a warm, direct tone.`;
// 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 async function handleChat(messages: ChatMessage[]): Promise<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 response = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 512,
system: SYSTEM_PROMPT,
messages: messages.map(m => ({ role: m.role, content: m.content })),
});
const apiMessages: Anthropic.MessageParam[] = messages.map(m => ({
role: m.role,
content: m.content,
}));
const block = response.content[0];
if (block.type !== 'text') throw new Error('Unexpected response type');
return block.text;
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 };
}

View File

@@ -1988,8 +1988,8 @@ app.post('/api/chat', async (req, res) => {
return;
}
try {
const reply = await handleChat(messages);
res.json({ reply });
const { reply, toolsUsed } = await handleChat(messages);
res.json({ reply, toolsUsed });
} catch (err) {
console.error('[chat] error:', (err as Error).message);
res.status(500).json({ error: 'Chat unavailable' });