import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { createClient, SupabaseClient } from "@supabase/supabase-js";
type Env = {
SUPABASE_URL: string;
SUPABASE_KEY: string;
MCP_OBJECT: DurableObjectNamespace<CommunityArchiveMCP>;
};
export class CommunityArchiveMCP extends McpAgent<Env> {
server = new McpServer({
name: "community-archive",
version: "1.0.0",
});
private supabase!: SupabaseClient;
async init() {
this.supabase = createClient(
this.env.SUPABASE_URL,
this.env.SUPABASE_KEY
);
// Tool: Search the archive
this.server.tool(
"search-archive",
"Search the Community Archive for tweets matching a keyword or phrase. Returns tweets from all archived Twitter users.",
{
query: z.string().describe("Search query (keyword, phrase, or hashtag)"),
limit: z.number().min(1).max(50).default(20).describe("Maximum results to return"),
},
async ({ query, limit }) => {
const { data, error } = await this.supabase
.from("tweets")
.select(`
tweet_id,
full_text,
created_at,
favorite_count,
retweet_count,
account:account_id (
username,
account_display_name
)
`)
.ilike("full_text", `%${query}%`)
.order("created_at", { ascending: false })
.limit(limit);
if (error) {
return {
content: [{ type: "text", text: `Error searching tweets: ${error.message}` }],
isError: true,
};
}
if (!data || data.length === 0) {
return {
content: [{ type: "text", text: `No tweets found matching "${query}"` }],
};
}
const results = data.map((tweet: any) => ({
id: tweet.tweet_id,
text: tweet.full_text,
author: tweet.account?.username || "unknown",
displayName: tweet.account?.account_display_name || "",
createdAt: tweet.created_at,
likes: tweet.favorite_count,
retweets: tweet.retweet_count,
}));
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2),
},
],
};
}
);
// Tool: Look up a profile
this.server.tool(
"lookup-profile",
"Look up a Twitter user's profile in the Community Archive. Returns bio, location, website, and account details.",
{
username: z.string().describe("Twitter username (without @)"),
},
async ({ username }) => {
// First get account info
const { data: account, error: accountError } = await this.supabase
.from("account")
.select("*")
.ilike("username", username)
.single();
if (accountError || !account) {
return {
content: [{ type: "text", text: `User "${username}" not found in archive` }],
isError: true,
};
}
// Then get profile info
const { data: profile } = await this.supabase
.from("profile")
.select("*")
.eq("account_id", account.account_id)
.single();
const result = {
username: account.username,
displayName: account.account_display_name,
accountId: account.account_id,
createdAt: account.created_at,
bio: profile?.bio || null,
location: profile?.location || null,
website: profile?.website || null,
avatarUrl: profile?.avatar_media_url || null,
};
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
);
// Tool: Browse a user's tweet history
this.server.tool(
"browse-user-history",
"Browse a specific user's tweet history from the Community Archive. Returns their tweets in reverse chronological order.",
{
username: z.string().describe("Twitter username (without @)"),
limit: z.number().min(1).max(100).default(20).describe("Maximum tweets to return"),
},
async ({ username, limit }) => {
// First get account_id from username
const { data: account, error: accountError } = await this.supabase
.from("account")
.select("account_id")
.ilike("username", username)
.single();
if (accountError || !account) {
return {
content: [{ type: "text", text: `User "${username}" not found in archive` }],
isError: true,
};
}
const { data: tweets, error } = await this.supabase
.from("tweets")
.select("tweet_id, full_text, created_at, favorite_count, retweet_count")
.eq("account_id", account.account_id)
.order("created_at", { ascending: false })
.limit(limit);
if (error) {
return {
content: [{ type: "text", text: `Error fetching tweets: ${error.message}` }],
isError: true,
};
}
if (!tweets || tweets.length === 0) {
return {
content: [{ type: "text", text: `No tweets found for user "${username}"` }],
};
}
const results = tweets.map((tweet: any) => ({
id: tweet.tweet_id,
text: tweet.full_text,
createdAt: tweet.created_at,
likes: tweet.favorite_count,
retweets: tweet.retweet_count,
}));
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
);
// Tool: Discover users in the archive
this.server.tool(
"discover-users",
"Discover which Twitter users are in the Community Archive. Can filter by username to find specific people.",
{
limit: z.number().min(1).max(200).default(50).describe("Maximum users to return"),
search: z.string().optional().describe("Optional: filter usernames containing this string"),
},
async ({ limit, search }) => {
let query = this.supabase
.from("account")
.select("username, account_display_name, created_at")
.order("username", { ascending: true })
.limit(limit);
if (search) {
query = query.ilike("username", `%${search}%`);
}
const { data: accounts, error } = await query;
if (error) {
return {
content: [{ type: "text", text: `Error listing users: ${error.message}` }],
isError: true,
};
}
if (!accounts || accounts.length === 0) {
return {
content: [{ type: "text", text: "No users found in archive" }],
};
}
const results = accounts.map((acc: any) => ({
username: acc.username,
displayName: acc.account_display_name,
joinedTwitter: acc.created_at,
}));
return {
content: [
{
type: "text",
text: `Found ${results.length} users:\n${JSON.stringify(results, null, 2)}`,
},
],
};
}
);
}
}
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
// Handle MCP endpoints
if (url.pathname === "/sse" || url.pathname === "/sse/message") {
return CommunityArchiveMCP.serveSSE("/sse").fetch(request, env, ctx);
}
if (url.pathname === "/mcp" || url.pathname.startsWith("/mcp/")) {
return CommunityArchiveMCP.serve("/mcp").fetch(request, env, ctx);
}
// Root path - show info
if (url.pathname === "/") {
return new Response(
JSON.stringify({
name: "Community Archive MCP Server",
description: "Query the Twitter Community Archive via MCP",
endpoints: {
sse: "/sse",
mcp: "/mcp",
},
tools: [
"search-archive - Search tweets across the archive",
"lookup-profile - Look up a user's profile",
"browse-user-history - Browse a user's tweets",
"discover-users - Find who's in the archive",
],
}, null, 2),
{
headers: { "Content-Type": "application/json" },
}
);
}
return new Response("Not found", { status: 404 });
},
};