Last active
April 27, 2025 06:02
-
-
Save laiso/8e45236886bd24dc865dde557db11465 to your computer and use it in GitHub Desktop.
tltr MCP Server on Cloudflare Workers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { McpAgent } from "agents/mcp"; | |
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
import { z } from "zod"; | |
import { GoogleGenerativeAI } from "@google/generative-ai"; | |
import { Readability } from '@mozilla/readability'; | |
import { parseHTML } from 'linkedom'; | |
type Env = { | |
MyMCP: DurableObjectNamespace<MyMCP>; | |
GEMINI_API_KEY: string; | |
}; | |
type State = {}; | |
export class MyMCP extends McpAgent<Env, State, {}> { | |
server = new McpServer({ | |
name: "tltr MCP Server", | |
description: "Summarize lengthy articles. Fetch content from a URL and provide a concise summary.", | |
version: "1.0.0", | |
}); | |
initialState: State = {}; | |
async init() { | |
this.server.tool( | |
"tltr", | |
"Summarize lengthy articles. Fetch content from a URL and provide a concise summary.", | |
{ url: z.string().url() }, | |
async ({ url }) => { | |
try { | |
const response = await fetch(url); | |
if (!response.ok) { | |
throw new Error(`Failed to fetch URL: ${response.statusText}`); | |
} | |
const html = await response.text(); | |
const textContent = extractTextFromHTML(html); | |
if (!textContent) { | |
return { content: [{ type: "text", text: "Could not extract text content from the URL." }] }; | |
} | |
const apiKey = this.env.GEMINI_API_KEY; | |
const { pageTitle, summary } = await summarizeContent(textContent, apiKey); | |
return { | |
content: [ | |
{ type: "text", text: `**${pageTitle}**\n\n${summary}` }, | |
], | |
}; | |
} catch (error) { | |
console.error("Error in tltr tool:", error); | |
const errorMessage = error instanceof Error ? error.message : String(error); | |
return { | |
content: [ | |
{ type: "text", text: `Error processing URL: ${errorMessage}` }, | |
], | |
}; | |
} | |
} | |
); | |
} | |
onStateUpdate(state: State) { | |
console.log({ stateUpdate: state }); | |
} | |
} | |
export default MyMCP.mount("/sse", { | |
binding: "MyMCP", | |
}) | |
export function extractTextFromHTML(html: string): string { | |
try { | |
const { document } = parseHTML(html); | |
const reader = new Readability(document); | |
const article = reader.parse(); | |
if (!article) return ""; | |
return `${article.title}\n\n${article.textContent}` || ""; | |
} catch (error) { | |
console.error("Failed to extract content using Readability:", error); | |
return ""; | |
} | |
} | |
export async function summarizeContent(content: string, apiKey: string): Promise<{ pageTitle: string, summary: string }> { | |
if (!apiKey) throw new Error("Gemini API key not configured"); | |
const genAI = new GoogleGenerativeAI(apiKey); | |
const systemInstruction = `Summarize the user's input. | |
## Instructions | |
- Start with a one-word conclusion. | |
- Then summarize the content in bullet points. | |
- Finally, generate a table of contents. | |
## Rules | |
- Start directly with the conclusion → summary without any preamble. | |
- Do not use headings like "Summary" in the response. | |
- Ensure no information is lost in the summary. | |
`; | |
const generationConfig = { | |
temperature: 0, | |
topP: 0.95, | |
topK: 40, | |
maxOutputTokens: 4192, | |
responseMimeType: "text/plain", | |
}; | |
const model = genAI.getGenerativeModel({ | |
model: "gemini-2.5-pro-exp-03-25", | |
systemInstruction, | |
generationConfig | |
}); | |
const result = await model.generateContent(content); | |
const summaryText = result.response.text(); | |
const contentLines = content.split('\n'); | |
const potentialTitle = contentLines.length > 0 ? contentLines[0].trim() : "Summary"; | |
const pageTitle = potentialTitle || "Summary"; | |
const summary = summaryText.trim(); | |
return { pageTitle, summary }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment