weone-daily-post
weone-daily-post — 远程 MCP 服务器
We One Aviation 每日 Instagram + Facebook 帖子的发布后端。 它是一个无状态的 Streamable HTTP MCP 服务器: Claude 负责思考(主题选择、文案、标题),此服务负责副作用(历史记录、图像渲染、存储、Graph API)。 图像是排版生成的,而非 AI 生成的:海报上的文字与提供的文字完全一致。
Claude ──POST /mcp (Bearer)──▶ Render web service (Node 20, Express)
├─ Supabase posts table + post-images bucket
├─ Chromium HTML template → JPEG
└─ Meta Graph IG container/publish, FB photos工具
工具 | 用途 |
| 最新优先的历史记录,最多 200 行: |
| 插入 |
| 将品牌化 HTML 模板渲染为精确尺寸的 sRGB JPEG,上传,并返回一个图像块以及公共 URL。 |
| 先发布到 Instagram,再发布到 Facebook,并记录结果。 |
| 影子模式:将完成的帖子记录为 |
| Meta 令牌到期前的天数 + 已授予的权限范围。 |
每个工具都返回 JSON。成功时返回 {"ok": true, ...};失败时返回 MCP 错误
结果,包含 {"ok": false, "error": {code, message, retryable, details}}。
不会向调用方抛出原始堆栈跟踪。
render_post
template 是 news、subject 或 career 之一 — 与 posts 表使用的三个类别相同。
字段 | 限制 | 说明 |
| 60 个字符 | Barlow 700,最多 3 行。句子大小写,非标题大小写。 |
| 3–4 项,每项 90 个字符 | Barlow 400,每项一个金色标记 |
| 32 个字符,可选 | 金色,由 CSS 转为大写,例如 |
| 90 个字符,可选 | 页脚栏左侧,例如 |
它返回两个内容块:一个图像块(base64 JPEG)和一个文本 块,包含公共 URL、文件名、尺寸、字节大小和渲染时间。
在上传任何内容之前会运行两个保护检查,两者都会指明违规字段:
长度限制,在触及 Chromium 之前检查 — 这是廉价的拒绝方式。
points[2] is 97 characters, limit is 90. Shorten it and retry.页面内测量,在布局之后 — 每个文本框都是一个固定大小的 裁剪框,如果其内容高于或宽于该框,渲染将被拒绝,并返回字段名称和溢出的像素数。这能捕获字符计数无法发现的问题, 例如一个长度合法但超出边缘的不可断开的 80 字符标记。
任一保护检查触发时都不会上传任何内容,因此拒绝只需一秒钟, 修复方法始终是"缩短指定字段"。
图像块仍然会返回,以便您能在上下文中阅读文字,但它不再是正确性检查: 固定模板不会拼错单词或虚构图表。最坏情况的合法输入(58 字符标题加四条 90 字符要点、最宽的合法眉题和页脚)已验证可适配所有三个模板。
publish_socials,逐步说明
对
image_url执行HEAD请求并断言200+content-type: image/jpeg。Meta 会在服务器端获取此 URL,而错误的 URL 会在数小时后以不透明的方式失败。 (拒绝HEAD的存储会改用单字节范围GET。)Instagram —
full_caption = caption + "\n\n" + hashtags.join(' '),上限 为 2200 个字符。只会从末尾丢弃标签;标题正文 永远不会被截断。如果正文单独超过 2200 个字符,调用会在发布任何内容之前以caption_too_long失败。POST {IG_USER_ID}/media→ 每秒轮询一次GET {container}?fields=status_code,status,最多 60 秒 → 仅在FINISHED时发布。在ERROR时,status字符串会原样返回,因为这是 Meta 解释其不满之处的唯一位置。Facebook — 使用
url和message执行POST {FB_PAGE_ID}/photos。无论 Instagram 结果如何都会尝试。记录 —
posts行会获得ig_post_id、fb_post_id、image_url和status=published(两者都成功)、partial(其中一个成功)或failed(两者都失败)。
返回 {ig_post_id, fb_post_id, status, errors: [...]}。单平台失败
绝不会被吞没:它会出现在 errors[] 中,包含平台、失败的阶段以及 Meta 自己的 code / error_subcode / message。
Related MCP server: Social Analytics MCP Server
环境变量
变量 | 必需 | 说明 |
| 是 |
|
| 是 |
|
| 是 | 服务角色密钥。绕过 RLS — 仅限服务器端。切勿将其放入连接器配置中。 |
| 否(默认 | 每次调用使用的 Graph API 版本。 |
| 用于发布 | Instagram 商业账户 ID(数字,非 @用户名)。 |
| 用于发布 | 与该 Instagram 账户关联的 Facebook 主页 ID。 |
| 用于发布 | 长期有效的主页访问令牌,具有 |
| 否 | Render 会设置此项。默认 10000。 |
| 否(默认 | 超过此大小后,内联 base64 预览会被缩小。 |
| 否 | Chrome/Chromium 二进制的显式路径。覆盖各平台默认值。 |
| 否 | 设置为 |
将 .env.example 复制为 .env 以进行本地运行。.env 已被 gitignore — 请保持这样。
设置
1. Supabase
在 SQL 编辑器中运行 migrations/001_init.sql(或执行 supabase db push)。它是
幂等的,并创建:
posts表,包含检查约束和topic上的唯一索引 — 该索引就是重复保护,因此重复保留应该失败,created_at desc和status索引,在
posts上启用 RLS,且无任何策略(只有服务密钥可以进入),公共
post-images存储桶及其公共读取策略。 公共读取是必需的:Meta 自行获取 JPEG,无法提供凭据。
2. Meta
您需要一个与 Facebook 主页关联的 Instagram 商业或创作者账户,
以及一个具有上述权限范围的长期有效主页令牌。在首次运行前使用
check_token() 确认 — 令牌过期是早晨发布失败最常见的原因。
3. 部署到 Render
使用 render.yaml(Blueprint):
将此仓库推送到 GitHub。
Render 仪表板 → 新建 → Blueprint → 选择仓库。它会读取
render.yaml:Node 20、npm ci && npm run build、npm start、 在/health上进行健康检查。Render 会提示输入每个
sync: false变量。粘贴它们。部署,然后检查日志中是否有
server.listening ... auth=configured。auth=MISSING表示MCP_AUTH_TOKEN未设置。
手动方式:
新建 → Web 服务 → 连接仓库。
运行时 Node,构建
npm ci && npm run build,启动npm start。健康检查路径
/health。添加上表中的环境变量,外加
NODE_VERSION=20。
验证:
curl https://<your-service>.onrender.com/health
# {"status":"ok","server":{...},"tools":[...six...],"uptime_s":3}使用 Starter 计划,不要用免费版。 Chromium 在 Node 之上大约需要 400 MB 常驻内存,而免费实例只有 512 MB——它会在渲染中途 OOM,失败表现为一个死掉的 worker,而不是有用的日志行。免费版还会在无活动后休眠,所以每天第一次工具调用还要额外付出 30–60 秒的冷启动。render.yaml 出于这两个原因设置了 starter。
构建时不需要下载浏览器:@sparticuz/chromium 自带二进制文件作为依赖,所以 npm ci && npm run build 就是整个构建。该构建步骤还会把 src/templates/ 复制到 dist/——tsc 只生成 .ts,没有这一步服务器能正常启动,但第一次渲染时会因缺少模板文件而失败。
4. 连接到 Claude
端点是:
https://<your-service>.onrender.com/mcp带有头:
Authorization: Bearer <MCP_AUTH_TOKEN>Claude Code / Cowork CLI:
claude mcp add --transport http weone-social \
https://<your-service>.onrender.com/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>".mcp.json(项目级,不带 token 提交):
{
"mcpServers": {
"weone-social": {
"type": "http",
"url": "https://<your-service>.onrender.com/mcp",
"headers": { "Authorization": "Bearer ${MCP_AUTH_TOKEN}" }
}
}
}在 Claude 桌面/网页 自定义连接器 对话框中,粘贴相同的 /mcp URL,并将 bearer token 放入请求头字段。认证设计上仅通过请求头——token 永远不会作为查询参数接受,因为 URL 会出现在代理日志和浏览器历史中。
每日运行说明(品牌规则、禁止声明、类别轮换、图片规格、QA 清单)位于 weone-daily-post 技能中,不在此服务器中。本服务刻意不持有任何编辑策略。
本地开发
npm install
cp .env.example .env # fill it in
npm run dev # tsx watch, http://localhost:10000
npm run typecheck
npm run build && npm startnpm run smoke
npm run smoke # render all three templates, upload, print 3 URLs
npm run smoke -- --no-upload # render locally only, no credentials needed渲染每种模板各一张,将三张 JPEG 写入 ./out,上传它们,对每个公开 URL 执行 HEAD 检查并打印三个链接。然后证明两个守卫仍然触发。它不触碰任何 Meta 端点,因此对生产凭据是安全的。上传只需要 SUPABASE_URL 和 SUPABASE_SERVICE_KEY;--no-upload 则什么都不需要。
本地文件在上传前写入,所以即使 Supabase 失败,仍有东西可看。
渲染管线
无头 Chromium 通过 file:// 加载 src/templates/{template}.html,值被写入 DOM,然后对页面截图。相同的输入总是产生相同的像素。
模板 位于
src/templates/。tokens.css保存所有颜色;base.css保存三者共享的骨架。模板文件与其兄弟文件的区别仅在于眉题处理和点标记(新闻:金色规则,主题:编号金色圆圈,职业:金色 V 形)。字体自托管 在
src/templates/fonts/(Barlow 400/600/700 用于所有内容,Cinzel 600 仅用于字标,拉丁子集,OFL)。渲染时不获取任何内容——网络调用会使输出不确定,并在 Render 上静默失败,回退到系统衬线字体。渲染器等待document.fonts.ready,然后断言两个字体确实加载,而不是截图回退字体。用户文本永远不会拼接进标记。 值通过
textContent和createElement进入,所以没有转义错误:标题中的<script>会以字面字符形式出现在海报上。视口 1080×1350,
deviceScaleFactor: 2,所以截图是 2160×2700 并降采样——文字边缘保持清晰。sharp:
resize(1080, 1350, {fit:'cover'})→toColorspace('srgb')→jpeg({quality: 90, chromaSubsampling: '4:4:4'}),剥离元数据。4:4:4 不是装饰——4:2:0 会弄脏彩色文字边缘,而这些海报就是文字。断言编码后的 JPEG 小于 8 MB,并且解码后的尺寸确实是所要求的。
上传为
{yyyy-mm-dd}-{slug}-{6 hex}.jpg(UTC 日期)。每次渲染都有自己的键,永远不会被覆盖——upsert: false。重新渲染一个主题不能改变已经发布先前 URL 的帖子下的图片。cacheControl为 60 秒也是出于同样原因:坏对象在一分钟内可纠正,而不是在 CDN 中固定一年。Meta 在上传后不久、服务器端获取一次 URL,所以不需要长缓存。对象会累积;存储远比帖子上的过时图片便宜。
布局行为
类型缩放到点数。 三个点得到 68px 标题和 36px 正文;四个点得到 60px 和 32px。这是用 CSS 的 :has() 完成的,所以布局决策完全在模板中,渲染器既不知道也不关心。溢出守卫在缩放后运行,所以测量的是缩放后的结果。
内容块在头部规则和底部栏之间垂直居中。 仅靠固定间隙无法保持填充目标,因为文本量变化,所以三个弹性元素共享剩余空间:上方带、下方带和标题下方的间隙。带硬上限为 150px,这强制执行“无大空白边距”;一旦达到上限,多余空间进入标题间隙,读起来像呼吸空间而不是空洞。
代表性内容上的测量垂直填充:74–79%,带 99–124px。一个刻意稀疏的情况(一行标题,三个一行点)位于 68.8%,带处于 150px 上限——对于这么少的文本,这是算术最大值,再提高意味着点分散得太远,不再像列表。
标题是 Barlow 700 句子大小写,行高 1.1,字距 −0.5px。Cinzel 仅存在于“WE ONE AVIATION”字标中。句子大小写不在代码中强制执行——机械地小写标题会破坏 DGCA、ATPL 和 AAI——所以它在 headline 字段描述中指定。
每个模板内联一个扁平 SVG 装饰:宽对角线规则(新闻)、同心罗盘弧(主题)、上升 V 形堆叠(职业)。金色 7%,从右下角文字后面溢出。它们的存在是为了在缩略图尺寸下给构图重量,并且太淡,不会影响文字对比度。
装饰位于
.anchor-wrap内部,一个用overflow: hidden固定到画布的盒子。没有它,绝对定位的图形会超出底部边缘,计入body.scrollHeight,溢出守卫会以恒定的 160px 页面溢出拒绝每次渲染。
标志
src/templates/assets/logo.png 是提供的组合标志:星形/飞机标记在“WE ONE AVIATION”字标上方。头部用 Cinzel 自己渲染该字标,所以 scripts/prepare-logo.mjs 派生 logo-mark.png——仅标记——以避免品牌名称出现两次。它找到非透明像素的水平带并保留最高的,所以以其他分辨率重新导出标志仍然有效。替换 logo.png 后:
npm run prepare-logo浏览器生命周期和内存
一个 Chromium 在进程生命周期内共享,仅在断开连接时重新启动。启动大约需要一秒和几百 MB,对于每篇帖子来说太昂贵了。
渲染内存。 Chromium 在 Node 之上大约需要 400 MB 常驻内存。免费实例是 512 MB,会因此 OOM——部署在渲染中途死亡,没有有用的日志行。使用 Starter 计划。 如果必须留在免费版,预期会重启,并将每次重启后的第一次渲染视为冷启动。
二进制文件来自哪里取决于主机:
主机 | 来源 |
设置了 | 该路径,始终优先 |
Linux(Render) |
|
macOS / 开发 |
|
刻意不使用 --single-process。 它与重用单个浏览器不兼容:在该标志下关闭 BrowserContext 会销毁整个浏览器,所以第二次渲染会失败,错误为 "Target page, context or browser has been closed"。在此代码库上测量:使用它时 3 个上下文中只有 1 个存活,不使用则 3/3 存活。重用是交易中更有价值的一半。如果主机要求,设置 CHROMIUM_SINGLE_PROCESS=1 强制重新启用,并预期每次启动只渲染一次。
错误处理
代码 | 含义 |
| 参数验证失败。 |
| 主题已存在。按设计工作——换一个。 |
| 没有该 |
| Supabase 拒绝了。 |
| 提供商或 sharp 问题。 |
| Meta 将获取的 URL 不是可访问的 JPEG。 |
| 仅标题正文超过 2200 字符。标签自动修剪;正文从不修剪。 |
| Graph API。 |
| 某物超出其预算(图片 60 秒,容器轮询 60 秒,Graph 30 秒)。 |
| 缺少必需的环境变量。 |
Meta 代码 190 和 200 永不重试。 190 是过期或无效的 token,200 是缺少权限;两者都需要人工干预,重试只会消耗速率限制并隐藏真正原因。这些错误返回 retryable: false 和 needs_human 注释,说明该做什么。
每次工具调用记录 tool.start 和 tool.ok/tool.error 及持续时间,每次 Graph 调用记录 graph.call 及方法、端点、状态和耗时毫秒——所以 Render 的日志查看器足以重建运行。
故障排除
症状 | 原因 |
每次请求都返回 | 缺少 Header,或令牌与 |
| 服务上未设置 |
|
|
IG 容器在 | Meta 无法获取图片,或响应缓慢。先在浏览器中检查该 URL。 |
| 令牌已过期。生成一个新的长期 Page 令牌。 |
| 缺少权限范围 — 重新授予 |
| 不是 bug。该主题已存在于 |
当天首次调用超时 | 免费套餐冷启动。先访问 |
渲染中途进程退出,无错误信息 | 内存不足。Chromium 需要约 400 MB;免费实例为 512 MB。请迁移到 |
| 符合预期。缩短该字段名并重新调用 |
|
|
| 已设置 |
Render 上找不到模板文件 | 跳过了 |
布局
src/
server.ts Express, bearer auth, /health, POST /mcp
config.ts Lazy env resolution, constants
log.ts Timed stdout logging
errors.ts AppError / MetaError, the no-retry rule for 190 & 200
supabase.ts posts CRUD + storage upload
meta.ts Graph client, IG container flow, FB photos, debug_token
image.ts Chromium lifecycle, template render, overflow guard, sharp
tools/
register.ts Timing, error envelope, content-block shaping
get_past_topics.ts reserve_topic.ts render_post.ts
publish_socials.ts mark_draft.ts check_token.ts
index.ts
templates/
tokens.css base.css design tokens + shared skeleton
news.html subject.html career.html
fonts/ barlow-400/600/700, cinzel-600 (woff2, self-hosted)
assets/ logo.png (supplied), logo-mark.png (derived)
migrations/001_init.sql
scripts/
smoke.ts render all three, upload, prove both guards fire
copy-templates.mjs build step: tsc emits only .ts, templates must reach dist/
prepare-logo.mjs derives logo-mark.png from logo.png
render.yamlThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI assistants to manage Instagram and Threads accounts — publish content, handle comments, view insights, search hashtags, and manage DMs through the Meta Graph API.594610MIT
- AlicenseAqualityDmaintenanceEnables Instagram and Facebook analytics via the Meta Graph API, offering account insights, media analytics, and post engagement data.196MIT
- AlicenseAqualityDmaintenanceMCP server to safely publish posts to multiple Facebook Pages via Meta Graph API, with built-in guardrails for brand voice, banned topics, image requirements, and anti-duplication.4MIT
- FlicenseNot gradedqualityCmaintenanceMCP server for automatic Instagram publishing — single image, carousel and Reels — via the official Instagram Graph API.40
Related MCP Connectors
Create, schedule and publish social posts to TikTok, Instagram, Facebook and YouTube.
Schedule and publish social posts to 11 platforms with media, campaigns, analytics and AI captions
Boost posts and launch community growth campaigns from your AI assistant. OAuth, credit-billed.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Weone404/mcp-content-cron'
If you have feedback or need assistance with the MCP directory API, please join our Discord server