memeops-mcp-server / server.py
fmarky's picture
feat: demo memeops mcp server with gui for doc
1219621
#!/usr/bin/env python3
import os
from fastmcp import FastMCP, Context
from starlette.responses import FileResponse
from huggingface_hub import InferenceClient
from typing_extensions import TypedDict
# --- Config (same as your Gradio app) ----------------------------------------
HF_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN", "")
HF_MODEL = os.getenv("HF_MODEL_ID", "meta-llama/Llama-3.1-8B-Instruct")
# Single HF client, chat-only
hf = InferenceClient(model=HF_MODEL, token=(HF_TOKEN or None))
# --- Minimal sampling fallback (server-side) ---------------------------------
def sampling_handler(messages, params, ctx):
msgs = [
{
"role": getattr(m, "role", "user"),
"content": getattr(m.content, "text", str(m.content)),
}
for m in messages
]
r = hf.chat.completions.create(messages=msgs, temperature=1, max_tokens=150)
out = r.choices[0].message.content
return (
"".join(p.get("text", "") for p in out)
if isinstance(out, list)
else out.strip()
)
# --- FastMCP app -------------------------------------------------------------
mcp = FastMCP(
"MemeOps 🚀",
sampling_handler=sampling_handler,
sampling_handler_behavior="fallback",
)
@mcp.prompt
def ship_meme_for_commit(commit_id: str, git_diff_content: str) -> str:
return (
"You are a release assistant for memes.\n"
"Given the commit and its git diff, CALL the tool `memes_render_for_commit`.\n"
f"commit_id: {commit_id}\n"
f"git_diff:\n{git_diff_content}\n"
"Return the whole tool response."
)
@mcp.resource("gitdiff://{commit_id}")
def git_diff(commit_id: str, ctx: Context) -> str:
return f"""--- MOCK DIFF for {commit_id}
--- a/main.py
+++ b/main.py
@@ -5,7 +5,6 @@
def process(data):
- result = data.strip().lower()
- return result
+ return data
"""
@mcp.resource("config://app")
def get_config() -> str:
"""Static configuration data"""
return "App configuration here"
class MemeResponse(TypedDict):
commit_id: str
caption: str
meme_name: str
image_url: str
@mcp.tool("memes_render_for_commit")
async def render_meme_for_commit(commit_id: str, ctx: Context) -> MemeResponse:
res = await ctx.read_resource(f"gitdiff://{commit_id}")
diff_text = getattr(res, "text", "")
# Caption via sampling (handled by our server fallback)
cap = await ctx.sample(
f"Write ONE short funny caption that can be linked to a bad commit, use the commit name for it :\n{diff_text} \n\n Write ONLY the funny caption!"
)
caption = getattr(cap, "text", str(cap)).strip()
# Pick a meme via sampling too (kept simple)
meme_list = "- 61579 : One Does Not Simply\n- 181913649 : Drake Hotline Bling\n- 112126428 : Distracted Boyfriend"
choice = await ctx.sample(
f'Caption: "{caption}"\nPick best meme id from:\n{meme_list}\nReply ONLY the id'
)
meme_id = getattr(choice, "text", str(choice)).strip()
name_map = {
"61579": "One Does Not Simply",
"181913649": "Drake Hotline Bling",
"112126428": "Distracted Boyfriend",
}
url_map = {
"61579": "https://i.imgflip.com/1bij.jpg",
"181913649": "https://i.imgflip.com/30b1gx.jpg",
"112126428": "https://i.imgflip.com/1ur9b0.jpg",
}
meme_name = name_map.get(meme_id, "One Does Not Simply")
image_url = url_map.get(meme_id, "https://i.imgflip.com/1bij.jpg")
return {
"commit_id": commit_id,
"caption": caption,
"meme_name": meme_name,
"image_url": image_url,
}
# Optional: serve a simple page at /
@mcp.custom_route("/", methods=["GET"])
async def root(_):
return FileResponse("index.html") # put a static file next to server.py, or remove
if __name__ == "__main__":
mcp.run(
transport="streamable-http",
host="0.0.0.0",
port=int(os.getenv("PORT", "7860")),
path="/mcp",
)