- SignalDesk1小时前
Original Summary
虽然目前 ChatGPT chat 也能联网了,例如你给他一个链接,它大概能读到它的内容。但是这并不是真正的互联网访问,如果你追问一下,你会发现它其实不是实时的去访问得到结果,而是之前被抓取的内容,或者说是搜索引擎给出的搜索结果。 此外,其虽然有虚拟环境,有 curl 命令,但如果你让他尝试 curl (或者用 python / node 等请求)某个外部地址,会发现它的环境中,外部地址的 DNS 解析被屏蔽了,因此也不能正常的访问。 不过,网页版可以向其添加自定义 MCP 服务器(仅需网页版添加一次,之后网页版、app等都能用),这就很有用了:我们可以使用 Cloudflare Worker 实现一个实时抓取和 CURL 能力,之后 ChatGPT 在 chat 模式下也能正常的调用它们来进行网络请求了。 方法很简单: (这里建议你自己自建下面的两个 Worker,我给出的 Worker 不保证可用性,被滥用会关闭。自建方法后面给出) 在网页版“插件”的右上方,选择 “添加自定义 MCP 服务器” 分别添加这两个 MCP 端点: Web Reader 实时以 Markdown 形式,获取某个链接的内容 https://read.ravelloh.com/mcp 无需身份验证 CURL 以 MCP 的方式调用 CURL https://curl.ravelloh.com/mcp 无需身份验证 完成了,现在可以到聊天里让 ChatGPT 尽情测试了。不过,Web Reader 实际上只能处理静态站点,如果是动态站点的话,我还做了一套基于 Cloudflare Browser Use 的 MCP,可以实现“云浏览器”,这样你的 ChatGPT chat 就和 work 基本差不多了。不过它需要 Cloudflare Worker Paid 才能够每月免费试用 10 小时,如果大家感兴趣的多的话我再详细讲讲。 上面两个 MCP 建议自建,Worker 代码如下,可以让你的 AI 教你怎么部署它们。不需要自定义域名,使用 workers.dev 也可以。 Web Reader: export default { async fetch(request, env, ctx) { const url = new URL(request.url); // /mcp 提供无 OAuth 的 Streamable HTTP MCP 服务 if (url.pathname.replace(/\/+$/, "") === "/mcp") { return handleMcpRequest(request); } const targetUrl = url.searchParams.get("url"); // ========================================== // 1. 无参数时:输出给 AI 看的用法与安装指令 // ========================================== if (!targetUrl) { const aiInstructions = { _meta: "AI Agent Tool Definition", name: "fetch_webpage_to_markdown", description: "Fetch any web page URL and convert its HTML content into clean, readable Markdown format. Use this tool to read external links, articles, or documentation.", parameters: { type: "object", properties: { url: { type: "string", description: "The complete HTTP/HTTPS URL of the target webpage to read." } }, required: ["url"] }, installation_instruction_for_agent:
For direct HTTP use, register this URL as a tool/action and send a GET request with the target URL as the ?url= query parameter. For MCP, connect to ${url.origin}/mcp using Streamable HTTP. Authentication: none (no OAuth). The MCP server exposes fetch_webpage_to_markdown; pass the target HTTP/HTTPS page URL as the url argument., mcp_server: { name: "webpage-fetcher", transport: "streamable-http", endpoint:${url.origin}/mcp, authentication: "none", protocol_versions: ["2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"], tools: ["fetch_webpage_to_markdown"] }, example_request:${url.origin}${url.pathname}?url=https://example.com}; return new Response(JSON.stringify(aiInstructions, null, 2), { headers: { "Content-Type": "application/json;charset=UTF-8", "Access-Control-Allow-Origin": "" } }); } // ========================================== // 2. 有参数时:请求、校验与解析 // ========================================== try { // 校验 URL 格式 new URL(targetUrl); } catch (e) { return aiErrorResponse("Invalid URL format. Please provide a valid absolute URL starting with http:// or https://", 400); } try { // 模拟常见浏览器 UA,防止被基础防爬虫机制拦截 const response = await fetch(targetUrl, { headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8" }, redirect: "follow" }); if (!response.ok) { return aiErrorResponse(Failed to fetch the URL. HTTP Status: ${response.status} ${response.statusText}, response.status); } // 确保内容是 HTML const contentType = response.headers.get("content-type") || ""; if (!contentType.toLowerCase().includes("text/html") && !contentType.toLowerCase().includes("text/plain")) { return aiErrorResponse(Target URL does not return HTML. Received Content-Type: ${contentType}, 415); } const html = await response.text(); // 转换为 Markdown const markdown = htmlToMarkdown(html); // 直接返回 text/markdown,AI 能够完美解析这种纯文本格式,且极大节省 Token return new Response(markdown, { headers: { "Content-Type": "text/markdown;charset=UTF-8", "Access-Control-Allow-Origin": "" } }); } catch (error) { return aiErrorResponse(Network or parsing error occurred: ${error.message}, 500); } } }; // ========================================== // MCP Streamable HTTP endpoint (/mcp) // ========================================== const MCP_PROTOCOL_VERSION = "2026-07-28"; const MCP_LEGACY_PROTOCOL_VERSIONS = [ "2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07" ]; const MCP_SERVER_INFO = { name: "webpage-fetcher", version: "1.0.0" }; const MCP_TOOL = { name: "fetch_webpage_to_markdown", description: "Fetch an HTTP/HTTPS webpage and convert its HTML or plain text content into clean, readable Markdown.", inputSchema: { type: "object", properties: { url: { type: "string", description: "The complete HTTP/HTTPS URL of the webpage to read." } }, required: ["url"] } }; const MCP_CORS_HEADERS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Accept, MCP-Protocol-Version, Mcp-Method, Mcp-Name, Mcp-Session-Id", "Access-Control-Max-Age": "86400" }; async function handleMcpRequest(request) { if (request.method === "OPTIONS") { return new Response(null, { status: 204, headers: MCP_CORS_HEADERS }); } if (request.method !== "POST") { return new Response("Method Not Allowed. Use POST for MCP requests.", { status: 405, headers: { ...MCP_CORS_HEADERS, "Allow": "POST, OPTIONS", "Content-Type": "text/plain;charset=UTF-8" } }); } const contentType = request.headers.get("content-type") || ""; if (!contentType.toLowerCase().includes("application/json")) { return mcpJsonResponse({ error: "Content-Type must be application/json." }, 415); } let message; try { message = await request.json(); } catch { return mcpJsonResponse(mcpRpcError(null, -32700, "Parse error"), 400); } if (Array.isArray(message)) { return mcpJsonResponse(mcpRpcError(null, -32600, "Batch requests are not supported."), 400); } if (!message || typeof message !== "object" || message.jsonrpc !== "2.0" || typeof message.method !== "string") { return mcpJsonResponse(mcpRpcError- 情报分类:服务器与云资源
- 分类依据:内容涉及服务器、云资源或网络线路
- 信息来源:服务器 / LINUX DO - 最新话题
- 发布时间:2026/10/17 17:58:34
- 暂无回复