index.tsโข4.99 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
const server = new Server(
{
name: "growth-hacker-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Define tools
const TOOLS = [
{
name: "generate_hooks",
description: "Generate viral hooks for X (Twitter) threads based on a topic.",
inputSchema: {
type: "object",
properties: {
topic: { type: "string", description: "The topic to generate hooks for" },
},
required: ["topic"],
},
},
{
name: "format_thread",
description: "Split long text into a numbered X (Twitter) thread (1/x).",
inputSchema: {
type: "object",
properties: {
text: { type: "string", description: "The full text content" },
},
required: ["text"],
},
},
{
name: "audit_copy",
description: "Audit sales copy for Gumroad/Landing pages and give a score.",
inputSchema: {
type: "object",
properties: {
copy: { type: "string", description: "The sales copy text" },
},
required: ["copy"],
},
},
];
// List Tools Handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: TOOLS,
};
});
// Call Tool Handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "generate_hooks") {
const topic = String(args?.topic || "");
const templates = [
`Stop doing ${topic} the hard way. Here is the easy way... ๐งต`,
`I spent 100 hours learning ${topic} so you don't have to. Here is what I found... ๐`,
`Most people get ${topic} wrong. Here is how to do it right...`,
`The secret to ${topic} is not what you think. A thread ๐งต`,
`If you want to master ${topic}, read this thread...`,
`10 tools for ${topic} that feel illegal to know ๐คฏ`,
`How to crush ${topic} in 2024 (Step-by-step guide)`,
];
return {
content: [{ type: "text", text: JSON.stringify(templates, null, 2) }],
};
}
if (name === "format_thread") {
const text = String(args?.text || "");
const MAX_LENGTH = 260;
const sentences = text.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [text];
const tweets: string[] = [];
let currentTweet = "";
for (const sentence of sentences) {
const trimmed = sentence.trim();
if (!trimmed) continue;
if ((currentTweet + " " + trimmed).length > MAX_LENGTH) {
if (currentTweet) tweets.push(currentTweet);
currentTweet = trimmed;
} else {
currentTweet = currentTweet ? currentTweet + " " + trimmed : trimmed;
}
}
if (currentTweet) tweets.push(currentTweet);
const numberedTweets = tweets.map((t, i) => `${t} (${i + 1}/${tweets.length})`);
return {
content: [{ type: "text", text: JSON.stringify(numberedTweets, null, 2) }],
};
}
if (name === "audit_copy") {
const copy = String(args?.copy || "");
const powerWords = ["free", "guarantee", "proven", "secret", "easy", "instant", "limited", "exclusive", "bonus", "you", "new", "results"];
const lowerCopy = copy.toLowerCase();
const foundPowerWords = powerWords.filter(w => lowerCopy.includes(w));
let score = 50;
score += foundPowerWords.length * 5;
const suggestions = [];
if (foundPowerWords.length < 3) suggestions.push("Add more power words like 'Free', 'Guarantee', 'Proven'.");
if (copy.length < 100) {
score -= 10;
suggestions.push("Copy is too short. Elaborate on benefits.");
}
if (!lowerCopy.includes("call to action") && !lowerCopy.includes("buy") && !lowerCopy.includes("click")) {
suggestions.push("Make sure you have a clear Call to Action (CTA).");
}
score = Math.min(100, Math.max(0, score));
return {
content: [{ type: "text", text: JSON.stringify({ score, foundPowerWords, suggestions }, null, 2) }],
};
}
throw new Error(`Tool not found: ${name}`);
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Growth Hacker MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});