Files
hermes-mcp/docs/agent-tutorial.html
Garfield 4bf93d6763 feat: Slack platform + Claude-powered chat support widget
- Add Slack as customer-facing messaging platform (client, 4 MCP tools, dashboard card)
- Add /api/chat endpoint powered by Claude Haiku with SquareMCP system prompt
- Add embeddable chat-widget.js injected into all 3 sites (docs, app, www)
- Add ANTHROPIC_API_KEY, serve product/ as static files
- Update Platform type to include slack

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 10:44:24 -04:00

233 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Tutorial — SquareMCP Docs</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<nav class="site-nav">
<div class="nav-inner">
<a href="index.html" class="nav-logo"><span class="nav-logo-mark">S</span> SquareMCP</a>
<div class="nav-links">
<a href="getting-started.html">Getting started</a>
<a href="platforms.html">Platform guides</a>
<a href="agent-tutorial.html" class="active">Agent tutorial</a>
<a href="https://hermes.squaremcp.com/openapi-social.json" target="_blank">API reference ↗</a>
</div>
<a href="https://squaremcp.com" class="nav-cta" target="_blank">Open app</a>
</div>
</nav>
<div class="page">
<div class="hero">
<h1>Build a LinkedIn posting agent</h1>
<p>Real code: a Claude agent that researches a topic, drafts a post, and publishes it to LinkedIn — fully automated.</p>
</div>
<div class="callout">
<strong>What you'll build</strong>
A Node.js script that (1) takes a topic from the command line, (2) uses Claude to research and draft a LinkedIn post, (3) calls the SquareMCP <code>linkedin_create_post</code> tool to publish — all in one agentic loop.
</div>
<h2>Prerequisites</h2>
<ul>
<li>Node.js 18+ with ESM support</li>
<li>An <a href="https://console.anthropic.com" target="_blank">Anthropic API key</a></li>
<li>A SquareMCP Bearer token with LinkedIn connected (see <a href="getting-started.html">Getting started</a>)</li>
</ul>
<h2>Step 1 — Install dependencies</h2>
<pre><code>npm init -y
npm install @anthropic-ai/sdk @modelcontextprotocol/sdk</code></pre>
<h2>Step 2 — Create the agent</h2>
<p>Create <code>linkedin-agent.mjs</code>:</p>
<pre><code><span class="kw">import</span> Anthropic <span class="kw">from</span> <span class="str">'@anthropic-ai/sdk'</span>;
<span class="kw">import</span> { Client } <span class="kw">from</span> <span class="str">'@modelcontextprotocol/sdk/client/index.js'</span>;
<span class="kw">import</span> { StreamableHTTPClientTransport } <span class="kw">from</span> <span class="str">'@modelcontextprotocol/sdk/client/streamableHttp.js'</span>;
<span class="kw">const</span> SQUAREMCP_URL = <span class="str">'https://hermes.squaremcp.com/mcp'</span>;
<span class="kw">const</span> SQUAREMCP_TOKEN = process.env.<span class="cls">SQUAREMCP_TOKEN</span>; <span class="cmt">// your Bearer token</span>
<span class="kw">const</span> topic = process.argv[<span class="num">2</span>] ?? <span class="str">'AI trends in 2026'</span>;
<span class="cmt">// ── 1. Connect to SquareMCP ──────────────────────────────────────</span>
<span class="kw">const</span> transport = <span class="kw">new</span> <span class="cls">StreamableHTTPClientTransport</span>(<span class="kw">new</span> <span class="cls">URL</span>(SQUAREMCP_URL), {
requestInit: { headers: { Authorization: <span class="str">`Bearer <span class="kw">${</span>SQUAREMCP_TOKEN<span class="kw">}</span>`</span> } },
});
<span class="kw">const</span> mcpClient = <span class="kw">new</span> <span class="cls">Client</span>({ name: <span class="str">'linkedin-agent'</span>, version: <span class="str">'1.0.0'</span> });
<span class="kw">await</span> mcpClient.connect(transport);
<span class="cmt">// Fetch available tools from SquareMCP</span>
<span class="kw">const</span> { tools: mcpTools } = <span class="kw">await</span> mcpClient.<span class="fn">listTools</span>();
<span class="cmt">// Convert MCP tool descriptors to Anthropic tool format</span>
<span class="kw">const</span> anthropicTools = mcpTools.<span class="fn">map</span>(t => ({
name: t.name,
description: t.description,
input_schema: t.inputSchema,
}));
<span class="cmt">// ── 2. Run the agentic loop ──────────────────────────────────────</span>
<span class="kw">const</span> anthropic = <span class="kw">new</span> <span class="cls">Anthropic</span>();
<span class="kw">const</span> messages = [
{
role: <span class="str">'user'</span>,
content: <span class="str">`You are a LinkedIn content strategist. Your job:
1. Think about the topic: "<span class="kw">${</span>topic<span class="kw">}</span>"
2. Draft a compelling LinkedIn post (150-250 words, professional tone, 3-5 hashtags)
3. Call linkedin_create_post to publish it
4. Report back what was posted.
Write the post now and publish it.`</span>,
},
];
console.<span class="fn">log</span>(<span class="str">`\n🤖 Agent starting — topic: "<span class="kw">${</span>topic<span class="kw">}</span>"\n`</span>);
<span class="kw">while</span> (<span class="kw">true</span>) {
<span class="kw">const</span> response = <span class="kw">await</span> anthropic.messages.<span class="fn">create</span>({
model: <span class="str">'claude-opus-4-7'</span>,
max_tokens: <span class="num">1024</span>,
tools: anthropicTools,
messages,
});
<span class="kmt">// Append assistant turn</span>
messages.<span class="fn">push</span>({ role: <span class="str">'assistant'</span>, content: response.content });
<span class="kw">if</span> (response.stop_reason === <span class="str">'end_turn'</span>) {
<span class="kw">const</span> text = response.content
.<span class="fn">filter</span>(b => b.type === <span class="str">'text'</span>)
.<span class="fn">map</span>(b => b.text)
.<span class="fn">join</span>(<span class="str">'\n'</span>);
console.<span class="fn">log</span>(<span class="str">'\n✅ Agent finished:\n'</span>, text);
<span class="kw">break</span>;
}
<span class="kw">if</span> (response.stop_reason !== <span class="str">'tool_use'</span>) <span class="kw">break</span>;
<span class="cmt">// Execute each tool call against SquareMCP</span>
<span class="kw">const</span> toolResults = [];
<span class="kw">for</span> (<span class="kw">const</span> block <span class="kw">of</span> response.content) {
<span class="kw">if</span> (block.type !== <span class="str">'tool_use'</span>) <span class="kw">continue</span>;
console.<span class="fn">log</span>(<span class="str">`🔧 Calling <span class="kw">${</span>block.name<span class="kw">}</span>...`</span>);
<span class="kw">let</span> result;
<span class="kw">try</span> {
result = <span class="kw">await</span> mcpClient.<span class="fn">callTool</span>({ name: block.name, arguments: block.input });
console.<span class="fn">log</span>(<span class="str">` ✓ <span class="kw">${</span>JSON.<span class="fn">stringify</span>(result.content[<span class="num">0</span>]).<span class="fn">slice</span>(<span class="num">0</span>, <span class="num">120</span>)<span class="kw">}</span>...`</span>);
} <span class="kw">catch</span> (err) {
result = { content: [{ type: <span class="str">'text'</span>, text: <span class="str">`Error: <span class="kw">${</span>err.message<span class="kw">}</span>`</span> }] };
console.<span class="fn">error</span>(<span class="str">` ✗ <span class="kw">${</span>err.message<span class="kw">}</span>`</span>);
}
toolResults.<span class="fn">push</span>({
type: <span class="str">'tool_result'</span>,
tool_use_id: block.id,
content: result.content,
});
}
messages.<span class="fn">push</span>({ role: <span class="str">'user'</span>, content: toolResults });
}
<span class="kw">await</span> mcpClient.<span class="fn">close</span>();</code></pre>
<h2>Step 3 — Run it</h2>
<pre><code>export ANTHROPIC_API_KEY=sk-ant-...
export SQUAREMCP_TOKEN=your-bearer-token-here
node linkedin-agent.mjs "The future of AI agents in enterprise software"</code></pre>
<p>Expected output:</p>
<pre><code>🤖 Agent starting — topic: "The future of AI agents in enterprise software"
🔧 Calling linkedin_create_post...
✓ {"id":"urn:li:share:7194...","success":true}...
✅ Agent finished:
I've published the following LinkedIn post:
"Enterprise software is undergoing a quiet revolution..."
[full post text]
The post has been published successfully to your LinkedIn feed.</code></pre>
<h2>Step 4 — Extend it</h2>
<p>Now that you have the agentic loop working, you can extend it:</p>
<h3>Post to multiple platforms at once</h3>
<pre><code><span class="cmt">// Replace the user message with:</span>
content: <span class="str">`Draft a post about "<span class="kw">${</span>topic<span class="kw">}</span>" and publish it on both
LinkedIn (professional tone) and Twitter (punchy, max 280 chars, 2 hashtags).
Use linkedin_create_post and twitter_create_tweet.`</span></code></pre>
<h3>Schedule with a cron job</h3>
<pre><code><span class="cmt"># Post every weekday at 9am</span>
0 9 * * 1-5 SQUAREMCP_TOKEN=... ANTHROPIC_API_KEY=... \
node /path/to/linkedin-agent.mjs "$(cat /path/to/topics.txt | shuf -n1)"</code></pre>
<h3>React to inbound WhatsApp messages</h3>
<p>Configure a webhook in the SquareMCP dashboard. When a WhatsApp message arrives, SquareMCP POSTs to your server. Run the same agent loop but start with the inbound message as context:</p>
<pre><code><span class="kw">import</span> express <span class="kw">from</span> <span class="str">'express'</span>;
<span class="kw">import</span> crypto <span class="kw">from</span> <span class="str">'crypto'</span>;
<span class="kw">const</span> app = <span class="fn">express</span>();
app.<span class="fn">use</span>(express.<span class="fn">json</span>());
app.<span class="fn">post</span>(<span class="str">'/webhook'</span>, <span class="kw">async</span> (req, res) => {
<span class="cmt">// Verify signature</span>
<span class="kw">const</span> sig = req.headers[<span class="str">'x-squaremcp-signature'</span>];
<span class="kw">const</span> expected = <span class="str">`sha256=<span class="kw">${</span>crypto
.<span class="fn">createHmac</span>(<span class="str">'sha256'</span>, process.env.<span class="cls">WEBHOOK_SECRET</span>)
.<span class="fn">update</span>(JSON.<span class="fn">stringify</span>(req.body))
.<span class="fn">digest</span>(<span class="str">'hex'</span>)<span class="kw">}</span>`</span>;
<span class="kw">if</span> (sig !== expected) { res.<span class="fn">status</span>(<span class="num">401</span>).<span class="fn">end</span>(); <span class="kw">return</span>; }
res.<span class="fn">status</span>(<span class="num">200</span>).<span class="fn">end</span>(); <span class="cmt">// acknowledge immediately</span>
<span class="kw">const</span> { platform, data } = req.body;
console.<span class="fn">log</span>(<span class="str">`Inbound from <span class="kw">${</span>platform<span class="kw">}</span>: <span class="kw">${</span>data.text<span class="kw">}</span>`</span>);
<span class="cmt">// Run agent in background...</span>
<span class="fn">runAgent</span>(data).catch(console.error);
});
app.<span class="fn">listen</span>(<span class="num">3000</span>);</code></pre>
<h2>Going further</h2>
<div class="card-grid">
<a href="getting-started.html" class="card" style="text-decoration:none;color:inherit;">
<h4>← Getting started</h4>
<p>Configure Claude Desktop, Codex CLI, or opencode.</p>
</a>
<a href="platforms.html" class="card" style="text-decoration:none;color:inherit;">
<h4>Platform guides</h4>
<p>Connect TikTok, WhatsApp, Instagram, and more.</p>
</a>
<a href="https://hermes.squaremcp.com/openapi-social.json" target="_blank" class="card" style="text-decoration:none;color:inherit;">
<h4>API reference ↗</h4>
<p>Full tool schemas for every platform.</p>
</a>
<a href="https://docs.anthropic.com/en/api/tool-use" target="_blank" class="card" style="text-decoration:none;color:inherit;">
<h4>Anthropic tool use ↗</h4>
<p>Deep dive into Claude's tool-use API.</p>
</a>
</div>
</div>
<script src="https://hermes.squaremcp.com/chat-widget.js"></script>
</body>
</html>