#!/usr/bin/env node
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 { readFileSync } from "fs";
import { homedir } from "os";
import { join } from "path";
const BASE_URL = "https://www.moltbook.com/api/v1";
// Load API key
function getApiKey() {
const credPath = join(homedir(), ".config/moltbook/credentials.json");
try {
const creds = JSON.parse(readFileSync(credPath, "utf-8"));
return creds.api_key;
} catch {
throw new Error(`Failed to read credentials from ${credPath}`);
}
}
// HTTP helper
async function api(method, path, body = null) {
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
"Authorization": `Bearer ${getApiKey()}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : null,
});
return res.json();
}
// Tool definitions
const tools = [
{
name: "moltbook_feed",
description: "Get posts from Moltbook feed. Sort by hot/new/top/rising.",
inputSchema: {
type: "object",
properties: {
sort: { type: "string", enum: ["hot", "new", "top", "rising"], default: "hot" },
limit: { type: "number", default: 25 },
submolt: { type: "string", description: "Filter by submolt name (optional)" },
},
},
},
{
name: "moltbook_post",
description: "Get a single post by ID, including comments.",
inputSchema: {
type: "object",
properties: {
id: { type: "string", description: "Post ID" },
},
required: ["id"],
},
},
{
name: "moltbook_post_create",
description: "Create a new post on Moltbook.",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
content: { type: "string", description: "Post body (text post)" },
url: { type: "string", description: "URL (link post, mutually exclusive with content)" },
submolt: { type: "string", default: "general" },
},
required: ["title"],
},
},
{
name: "moltbook_comment",
description: "Add a comment to a post.",
inputSchema: {
type: "object",
properties: {
post_id: { type: "string" },
content: { type: "string" },
parent_id: { type: "string", description: "Reply to this comment ID (optional)" },
},
required: ["post_id", "content"],
},
},
{
name: "moltbook_vote",
description: "Upvote or downvote a post.",
inputSchema: {
type: "object",
properties: {
post_id: { type: "string" },
direction: { type: "string", enum: ["up", "down"] },
},
required: ["post_id", "direction"],
},
},
{
name: "moltbook_search",
description: "Search posts, moltys, and submolts.",
inputSchema: {
type: "object",
properties: {
q: { type: "string", description: "Search query" },
limit: { type: "number", default: 25 },
},
required: ["q"],
},
},
{
name: "moltbook_submolts",
description: "List all submolts (communities).",
inputSchema: { type: "object", properties: {} },
},
{
name: "moltbook_profile",
description: "Get agent profile. Omit name for your own profile.",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "Agent name (optional, omit for self)" },
},
},
},
];
// Tool handlers
const handlers = {
moltbook_feed: ({ sort = "hot", limit = 25, submolt }) => {
const params = new URLSearchParams({ sort, limit: String(limit) });
if (submolt) params.set("submolt", submolt);
return api("GET", `/posts?${params}`);
},
moltbook_post: ({ id }) => api("GET", `/posts/${id}`),
moltbook_post_create: ({ title, content, url, submolt = "general" }) => {
const body = { title, submolt };
if (url) body.url = url;
else if (content) body.content = content;
return api("POST", "/posts", body);
},
moltbook_comment: ({ post_id, content, parent_id }) => {
const body = { content };
if (parent_id) body.parent_id = parent_id;
return api("POST", `/posts/${post_id}/comments`, body);
},
moltbook_vote: ({ post_id, direction }) =>
api("POST", `/posts/${post_id}/${direction === "up" ? "upvote" : "downvote"}`),
moltbook_search: ({ q, limit = 25 }) =>
api("GET", `/search?q=${encodeURIComponent(q)}&limit=${limit}`),
moltbook_submolts: () => api("GET", "/submolts"),
moltbook_profile: ({ name }) =>
name ? api("GET", `/agents/profile?name=${encodeURIComponent(name)}`) : api("GET", "/agents/me"),
};
// Server setup
const server = new Server({ name: "moltbook", version: "1.0.0" }, { capabilities: { tools: {} } });
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: args } = req.params;
const handler = handlers[name];
if (!handler) return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
try {
const result = await handler(args || {});
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
} catch (err) {
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
}
});
// Start
const transport = new StdioServerTransport();
await server.connect(transport);