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:
@@ -50,6 +50,11 @@
|
|||||||
background: ${BRAND}; color: #fff; border-bottom-right-radius: 4px; align-self: flex-end;
|
background: ${BRAND}; color: #fff; border-bottom-right-radius: 4px; align-self: flex-end;
|
||||||
}
|
}
|
||||||
.smcp-msg.typing { color: #888; font-style: italic; }
|
.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 {
|
#smcp-input-row {
|
||||||
padding: 10px 12px; border-top: 1px solid #eee;
|
padding: 10px 12px; border-top: 1px solid #eee;
|
||||||
display: flex; gap: 8px; flex-shrink: 0;
|
display: flex; gap: 8px; flex-shrink: 0;
|
||||||
@@ -152,6 +157,12 @@
|
|||||||
indicator.textContent = reply;
|
indicator.textContent = reply;
|
||||||
indicator.classList.remove('typing');
|
indicator.classList.remove('typing');
|
||||||
history.push({ role: 'assistant', content: reply });
|
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 {
|
} catch {
|
||||||
indicator.textContent = 'Network error. Please try again.';
|
indicator.textContent = 'Network error. Please try again.';
|
||||||
indicator.classList.remove('typing');
|
indicator.classList.remove('typing');
|
||||||
|
|||||||
122
src/chat.ts
122
src/chat.ts
@@ -1,49 +1,123 @@
|
|||||||
import Anthropic from '@anthropic-ai/sdk';
|
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 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:
|
What SquareMCP does:
|
||||||
- Connects AI coding assistants to social platforms: LinkedIn, TikTok, WhatsApp, Instagram, Twitter/X, Facebook, Telegram, Discord, Slack, and Email
|
- 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
|
- 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
|
- 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
|
- Plans: Free (100 calls/month), Pro, Business
|
||||||
|
|
||||||
How it works:
|
When using tools:
|
||||||
1. Customer signs up at the SquareMCP dashboard
|
- For demo sends (Slack, Telegram, Discord), send a short, friendly message that explains this is a live SquareMCP demo
|
||||||
2. Connects their social accounts (bot tokens, API keys)
|
- After a tool succeeds, explain what just happened and how easy it was
|
||||||
3. Adds the SquareMCP MCP server to their AI client config
|
- If a tool fails (missing credentials), explain what credential the customer would set up instead
|
||||||
4. Their AI agent can now send messages, post content, read analytics — on any connected platform
|
- Never expose tokens or internal IDs in your reply
|
||||||
|
|
||||||
Common questions:
|
Keep replies concise. If you don't know something, say so and suggest emailing support@squaremcp.com.`;
|
||||||
- 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 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 {
|
export interface ChatMessage {
|
||||||
role: 'user' | 'assistant';
|
role: 'user' | 'assistant';
|
||||||
content: string;
|
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) {
|
if (!process.env.ANTHROPIC_API_KEY) {
|
||||||
throw new Error('ANTHROPIC_API_KEY not configured');
|
throw new Error('ANTHROPIC_API_KEY not configured');
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await client.messages.create({
|
const apiMessages: Anthropic.MessageParam[] = messages.map(m => ({
|
||||||
model: 'claude-haiku-4-5-20251001',
|
role: m.role,
|
||||||
max_tokens: 512,
|
content: m.content,
|
||||||
system: SYSTEM_PROMPT,
|
}));
|
||||||
messages: messages.map(m => ({ role: m.role, content: m.content })),
|
|
||||||
});
|
|
||||||
|
|
||||||
const block = response.content[0];
|
const toolsUsed: string[] = [];
|
||||||
if (block.type !== 'text') throw new Error('Unexpected response type');
|
const MAX_ITERATIONS = 5;
|
||||||
return block.text;
|
|
||||||
|
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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1988,8 +1988,8 @@ app.post('/api/chat', async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const reply = await handleChat(messages);
|
const { reply, toolsUsed } = await handleChat(messages);
|
||||||
res.json({ reply });
|
res.json({ reply, toolsUsed });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[chat] error:', (err as Error).message);
|
console.error('[chat] error:', (err as Error).message);
|
||||||
res.status(500).json({ error: 'Chat unavailable' });
|
res.status(500).json({ error: 'Chat unavailable' });
|
||||||
|
|||||||
Reference in New Issue
Block a user