Farcaster MCP Server
by manimohans
- src
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
var stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
var zod_1 = require("zod");
var axios_1 = require("axios");
var buffer_1 = require("buffer");
var process = require("process");
// Farcaster Hubble API base URL
var HUBBLE_API_BASE = "https://nemes.farcaster.xyz:2281/v1";
// Create MCP server with all capabilities
var server = new mcp_js_1.McpServer({
name: "farcaster-mcp",
version: "1.0.0",
capabilities: {
tools: {},
resources: {},
prompts: {}
}
});
// Helper function to make API requests to Farcaster Hubble
function fetchFromHubble(endpoint_1) {
return __awaiter(this, arguments, void 0, function (endpoint, params) {
var response, error_1;
var _a;
if (params === void 0) { params = {}; }
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
console.error("Fetching from ".concat(HUBBLE_API_BASE).concat(endpoint, " with params:"), params);
return [4 /*yield*/, axios_1.default.get("".concat(HUBBLE_API_BASE).concat(endpoint), {
params: params
})];
case 1:
response = _b.sent();
console.error("Response status: ".concat(response.status));
return [2 /*return*/, response.data];
case 2:
error_1 = _b.sent();
console.error("Error fetching from Hubble API:", error_1);
if (axios_1.default.isAxiosError(error_1) && error_1.response) {
throw new Error("Hubble API error: ".concat(error_1.response.status, " - ").concat(((_a = error_1.response.data) === null || _a === void 0 ? void 0 : _a.details) || error_1.message));
}
throw new Error("Failed to fetch from Hubble: ".concat(error_1 instanceof Error ? error_1.message : String(error_1)));
case 3: return [2 /*return*/];
}
});
});
}
// Helper function to get user data (username, display name)
function getUserData(fid) {
return __awaiter(this, void 0, void 0, function () {
var usernameResponse, displayNameResponse, pfpResponse, bioResponse, userData, error_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 5, , 6]);
return [4 /*yield*/, fetchFromHubble("/userDataByFid", {
fid: fid,
user_data_type: 1 // USERNAME
})];
case 1:
usernameResponse = _a.sent();
return [4 /*yield*/, fetchFromHubble("/userDataByFid", {
fid: fid,
user_data_type: 2 // DISPLAY_NAME
})];
case 2:
displayNameResponse = _a.sent();
return [4 /*yield*/, fetchFromHubble("/userDataByFid", {
fid: fid,
user_data_type: 3 // PFP
})];
case 3:
pfpResponse = _a.sent();
return [4 /*yield*/, fetchFromHubble("/userDataByFid", {
fid: fid,
user_data_type: 4 // BIO
})];
case 4:
bioResponse = _a.sent();
userData = { fid: fid, type: 0 };
if (usernameResponse.messages && usernameResponse.messages.length > 0) {
userData.username = usernameResponse.messages[0].data.userDataBody.value;
}
if (displayNameResponse.messages && displayNameResponse.messages.length > 0) {
userData.displayName = displayNameResponse.messages[0].data.userDataBody.value;
}
if (pfpResponse.messages && pfpResponse.messages.length > 0) {
userData.pfpUrl = pfpResponse.messages[0].data.userDataBody.value;
}
if (bioResponse.messages && bioResponse.messages.length > 0) {
userData.bio = bioResponse.messages[0].data.userDataBody.value;
}
return [2 /*return*/, userData];
case 5:
error_2 = _a.sent();
console.error("Error fetching user data:", error_2);
return [2 /*return*/, { fid: fid, type: 0 }];
case 6: return [2 /*return*/];
}
});
});
}
// Helper function to get reactions for a cast
function getReactionsForCast(castHash) {
return __awaiter(this, void 0, void 0, function () {
var likesResponse, recastsResponse, error_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, fetchFromHubble("/reactionsByCast", {
target_hash: castHash,
reaction_type: 1 // LIKE
})];
case 1:
likesResponse = _a.sent();
return [4 /*yield*/, fetchFromHubble("/reactionsByCast", {
target_hash: castHash,
reaction_type: 2 // RECAST
})];
case 2:
recastsResponse = _a.sent();
return [2 /*return*/, {
likes: likesResponse.messages ? likesResponse.messages.length : 0,
recasts: recastsResponse.messages ? recastsResponse.messages.length : 0
}];
case 3:
error_3 = _a.sent();
console.error("Error fetching reactions:", error_3);
return [2 /*return*/, { likes: 0, recasts: 0 }];
case 4: return [2 /*return*/];
}
});
});
}
// Helper function to format casts
function formatCasts(casts_1) {
return __awaiter(this, arguments, void 0, function (casts, limit) {
var castAdds, limitedCasts, formattedCastsPromises, formattedCasts;
var _this = this;
if (limit === void 0) { limit = 10; }
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!casts || casts.length === 0) {
return [2 /*return*/, "No casts found."];
}
console.error("Formatting ".concat(casts.length, " casts"));
castAdds = casts.filter(function (cast) {
return cast.data && cast.data.type === "MESSAGE_TYPE_CAST_ADD" && cast.data.castAddBody;
});
if (castAdds.length === 0) {
return [2 /*return*/, "No cast additions found."];
}
limitedCasts = castAdds.slice(0, limit);
formattedCastsPromises = limitedCasts.map(function (cast, index) { return __awaiter(_this, void 0, void 0, function () {
var fid, userData, reactions, farcasterEpoch, date, username, displayName, replyInfo, embedsInfo, error_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
// Check if cast and cast.data exist
if (!cast || !cast.data || !cast.data.castAddBody) {
console.error("Invalid cast at index ".concat(index, ":"), cast);
return [2 /*return*/, "".concat(index + 1, ". [Invalid cast format]")];
}
fid = cast.data.fid;
return [4 /*yield*/, getUserData(fid)];
case 1:
userData = _a.sent();
return [4 /*yield*/, getReactionsForCast(cast.hash)];
case 2:
reactions = _a.sent();
farcasterEpoch = new Date('2021-01-01T00:00:00Z').getTime() / 1000;
date = new Date((cast.data.timestamp + farcasterEpoch) * 1000).toLocaleString();
username = userData.username || 'unknown';
displayName = userData.displayName || username;
replyInfo = "";
if (cast.data.castAddBody.parentCastId) {
replyInfo = " Reply to: ".concat(cast.data.castAddBody.parentCastId.fid, "/").concat(cast.data.castAddBody.parentCastId.hash, "\n");
}
embedsInfo = "";
if (cast.data.castAddBody.embeds && cast.data.castAddBody.embeds.length > 0) {
embedsInfo = " Embeds: " + cast.data.castAddBody.embeds.map(function (embed) {
if (embed.url)
return embed.url;
return "embedded content";
}).join(", ") + "\n";
}
return [2 /*return*/, "\n".concat(index + 1, ". @").concat(username, " (").concat(displayName, ")\n FID: ").concat(fid, "\n Time: ").concat(date, "\n Text: ").concat(cast.data.castAddBody.text, "\n").concat(replyInfo).concat(embedsInfo, " Likes: ").concat(reactions.likes, " | Recasts: ").concat(reactions.recasts, "\n Cast ID: ").concat(cast.hash, "\n ")];
case 3:
error_4 = _a.sent();
console.error("Error formatting cast at index ".concat(index, ":"), error_4);
return [2 /*return*/, "".concat(index + 1, ". [Error formatting cast]")];
case 4: return [2 /*return*/];
}
});
}); });
return [4 /*yield*/, Promise.all(formattedCastsPromises)];
case 1:
formattedCasts = _a.sent();
return [2 /*return*/, formattedCasts.join("\n---\n")];
}
});
});
}
// Helper function to find FID by username
function getFidByUsername(username) {
return __awaiter(this, void 0, void 0, function () {
var response, error_5;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, fetchFromHubble("/userNameProofByName", {
name: username
})];
case 1:
response = _a.sent();
if (response && response.proof && response.proof.fid) {
return [2 /*return*/, response.proof.fid];
}
return [2 /*return*/, null];
case 2:
error_5 = _a.sent();
console.error("Error finding FID by username:", error_5);
return [2 /*return*/, null];
case 3: return [2 /*return*/];
}
});
});
}
// Main function to start the server
function main() {
return __awaiter(this, void 0, void 0, function () {
var transport, error_6;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
// Register tool for getting casts by user FID
server.tool("get-user-casts", "Get casts from a specific Farcaster user by FID", {
fid: zod_1.z.number().describe("Farcaster user ID (FID)"),
limit: zod_1.z.number().optional().describe("Maximum number of casts to return (default: 10)")
}, function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
var response, castsText, error_7;
var _c;
var fid = _b.fid, _d = _b.limit, limit = _d === void 0 ? 10 : _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
_e.trys.push([0, 3, , 4]);
console.error("Fetching casts for FID ".concat(fid, " with limit ").concat(limit));
return [4 /*yield*/, fetchFromHubble("/castsByFid", {
fid: fid,
pageSize: limit,
reverse: 1 // Get newest first
})];
case 1:
response = _e.sent();
console.error("Got response with ".concat(((_c = response.messages) === null || _c === void 0 ? void 0 : _c.length) || 0, " messages"));
if (!response.messages || response.messages.length === 0) {
return [2 /*return*/, {
content: [
{
type: "text",
text: "No casts found for FID ".concat(fid)
}
]
}];
}
return [4 /*yield*/, formatCasts(response.messages, limit)];
case 2:
castsText = _e.sent();
return [2 /*return*/, {
content: [
{
type: "text",
text: "# Casts from FID ".concat(fid, "\n\n").concat(castsText)
}
]
}];
case 3:
error_7 = _e.sent();
console.error("Error in get-user-casts:", error_7);
return [2 /*return*/, {
content: [
{
type: "text",
text: "Error fetching casts: ".concat(error_7 instanceof Error ? error_7.message : String(error_7))
}
],
isError: true
}];
case 4: return [2 /*return*/];
}
});
}); });
// Register tool for getting casts from a channel (parent URL)
server.tool("get-channel-casts", "Get casts from a specific Farcaster channel", {
channel: zod_1.z.string().describe("Channel name or parent URL"),
limit: zod_1.z.number().optional().describe("Maximum number of casts to return (default: 10)")
}, function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
var channelHash, response, castsText, error_8;
var channel = _b.channel, _c = _b.limit, limit = _c === void 0 ? 10 : _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_d.trys.push([0, 3, , 4]);
channelHash = channel.startsWith("0x") ? channel : "0x".concat(buffer_1.Buffer.from(channel).toString("hex"));
return [4 /*yield*/, fetchFromHubble("/castsByParent", {
parent_url: channelHash,
pageSize: limit,
reverse: 1 // Get newest first
})];
case 1:
response = _d.sent();
if (!response.messages || response.messages.length === 0) {
return [2 /*return*/, {
content: [
{
type: "text",
text: "No casts found for channel \"".concat(channel, "\"")
}
]
}];
}
return [4 /*yield*/, formatCasts(response.messages, limit)];
case 2:
castsText = _d.sent();
return [2 /*return*/, {
content: [
{
type: "text",
text: "# Recent Casts in Channel \"".concat(channel, "\"\n\n").concat(castsText)
}
]
}];
case 3:
error_8 = _d.sent();
console.error("Error in get-channel-casts:", error_8);
return [2 /*return*/, {
content: [
{
type: "text",
text: "Error fetching channel casts: ".concat(error_8 instanceof Error ? error_8.message : String(error_8))
}
],
isError: true
}];
case 4: return [2 /*return*/];
}
});
}); });
// Register tool for getting casts by username
server.tool("get-username-casts", "Get casts from a specific Farcaster username", {
username: zod_1.z.string().describe("Farcaster username"),
limit: zod_1.z.number().optional().describe("Maximum number of casts to return (default: 10)")
}, function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
var fid, response, castsText, error_9;
var username = _b.username, _c = _b.limit, limit = _c === void 0 ? 10 : _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_d.trys.push([0, 4, , 5]);
return [4 /*yield*/, getFidByUsername(username)];
case 1:
fid = _d.sent();
if (!fid) {
return [2 /*return*/, {
content: [
{
type: "text",
text: "User \"".concat(username, "\" not found.")
}
],
isError: true
}];
}
return [4 /*yield*/, fetchFromHubble("/castsByFid", {
fid: fid,
pageSize: limit,
reverse: 1 // Get newest first
})];
case 2:
response = _d.sent();
if (!response.messages || response.messages.length === 0) {
return [2 /*return*/, {
content: [
{
type: "text",
text: "No casts found for username \"".concat(username, "\" (FID: ").concat(fid, ")")
}
]
}];
}
return [4 /*yield*/, formatCasts(response.messages, limit)];
case 3:
castsText = _d.sent();
return [2 /*return*/, {
content: [
{
type: "text",
text: "# Casts from @".concat(username, " (FID: ").concat(fid, ")\n\n").concat(castsText)
}
]
}];
case 4:
error_9 = _d.sent();
console.error("Error in get-username-casts:", error_9);
return [2 /*return*/, {
content: [
{
type: "text",
text: "Error fetching casts by username: ".concat(error_9 instanceof Error ? error_9.message : String(error_9))
}
],
isError: true
}];
case 5: return [2 /*return*/];
}
});
}); });
// Register tool for searching casts (simplified - Hubble API doesn't have direct search)
server.tool("search-casts", "Search for casts by keyword (simplified implementation)", {
keyword: zod_1.z.string().describe("Keyword to search for in casts"),
limit: zod_1.z.number().optional().describe("Maximum number of casts to return (default: 10)")
}, function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
var response, keywordLower_1, filteredCasts, castsText, error_10;
var keyword = _b.keyword, _c = _b.limit, limit = _c === void 0 ? 10 : _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_d.trys.push([0, 3, , 4]);
return [4 /*yield*/, fetchFromHubble("/casts", {
pageSize: 100, // Fetch more to filter
reverse: 1 // Get newest first
})];
case 1:
response = _d.sent();
if (!response.messages || response.messages.length === 0) {
return [2 /*return*/, {
content: [
{
type: "text",
text: "No casts found to search through."
}
]
}];
}
keywordLower_1 = keyword.toLowerCase();
filteredCasts = response.messages.filter(function (cast) {
return cast.data &&
cast.data.type === "MESSAGE_TYPE_CAST_ADD" &&
cast.data.castAddBody &&
cast.data.castAddBody.text.toLowerCase().includes(keywordLower_1);
}).slice(0, limit);
if (filteredCasts.length === 0) {
return [2 /*return*/, {
content: [
{
type: "text",
text: "No casts found containing \"".concat(keyword, "\".")
}
]
}];
}
return [4 /*yield*/, formatCasts(filteredCasts, limit)];
case 2:
castsText = _d.sent();
return [2 /*return*/, {
content: [
{
type: "text",
text: "# Search Results for \"".concat(keyword, "\"\n\n").concat(castsText)
}
]
}];
case 3:
error_10 = _d.sent();
console.error("Error in search-casts:", error_10);
return [2 /*return*/, {
content: [
{
type: "text",
text: "Error searching casts: ".concat(error_10 instanceof Error ? error_10.message : String(error_10))
}
],
isError: true
}];
case 4: return [2 /*return*/];
}
});
}); });
// Register resource for trending casts (simplified)
server.resource("trending-casts", "trending://casts", function (uri) { return __awaiter(_this, void 0, void 0, function () {
var response, castsText, error_11;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, fetchFromHubble("/casts", {
pageSize: 10,
reverse: 1 // Get newest first
})];
case 1:
response = _a.sent();
if (!response.messages || response.messages.length === 0) {
return [2 /*return*/, {
contents: [{
uri: uri.href,
text: "# Recent Casts on Farcaster\n\nNo casts found."
}]
}];
}
return [4 /*yield*/, formatCasts(response.messages, 10)];
case 2:
castsText = _a.sent();
return [2 /*return*/, {
contents: [{
uri: uri.href,
text: "# Recent Casts on Farcaster\n\n".concat(castsText)
}]
}];
case 3:
error_11 = _a.sent();
console.error("Error fetching recent casts:", error_11);
throw new Error("Failed to fetch recent casts: ".concat(error_11 instanceof Error ? error_11.message : String(error_11)));
case 4: return [2 /*return*/];
}
});
}); });
// Register prompt for cast analysis
server.prompt("analyze-cast", "Analyze a Farcaster cast", {
cast_hash: zod_1.z.string().describe("Hash of the cast to analyze")
}, function (args) { return __awaiter(_this, void 0, void 0, function () {
var response, cast, userData, reactions, farcasterEpoch, date, error_12;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 4, , 5]);
return [4 /*yield*/, fetchFromHubble("/castById", {
hash: args.cast_hash
})];
case 1:
response = _a.sent();
if (!response.message) {
throw new Error("Cast not found");
}
cast = response.message;
// Check if this is a cast add message
if (cast.data.type !== "MESSAGE_TYPE_CAST_ADD" || !cast.data.castAddBody) {
throw new Error("This is not a cast addition message");
}
return [4 /*yield*/, getUserData(cast.data.fid)];
case 2:
userData = _a.sent();
return [4 /*yield*/, getReactionsForCast(cast.hash)];
case 3:
reactions = _a.sent();
farcasterEpoch = new Date('2021-01-01T00:00:00Z').getTime() / 1000;
date = new Date((cast.data.timestamp + farcasterEpoch) * 1000).toLocaleString();
return [2 /*return*/, {
messages: [{
role: "user",
content: {
type: "text",
text: "Please analyze this Farcaster cast:\n \nAuthor: @".concat(userData.username || 'unknown', " (").concat(userData.displayName || 'Unknown', ")\nFID: ").concat(cast.data.fid, "\nTime: ").concat(date, "\nText: ").concat(cast.data.castAddBody.text, "\nLikes: ").concat(reactions.likes, " | Recasts: ").concat(reactions.recasts, "\n\nPlease provide insights on:\n1. The main topic or themes of this cast\n2. The sentiment (positive, negative, neutral)\n3. Any notable context or references\n4. The engagement level compared to typical Farcaster casts")
}
}]
}];
case 4:
error_12 = _a.sent();
console.error("Error in analyze-cast prompt:", error_12);
return [2 /*return*/, {
messages: [{
role: "user",
content: {
type: "text",
text: "Error fetching cast: ".concat(error_12 instanceof Error ? error_12.message : String(error_12))
}
}]
}];
case 5: return [2 /*return*/];
}
});
}); });
transport = new stdio_js_1.StdioServerTransport();
return [4 /*yield*/, server.connect(transport)];
case 1:
_a.sent();
console.error("Farcaster MCP Server running on stdio");
return [3 /*break*/, 3];
case 2:
error_6 = _a.sent();
console.error("Fatal error in main():", error_6);
process.exit(1);
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
});
}
main().catch(function (error) {
console.error("Unhandled error:", error);
process.exit(1);
});