Skip to main content
Glama
Weone404

weone-daily-post

by Weone404

weone-daily-post — 원격 MCP 서버

We One Aviation 일일 Instagram + Facebook 게시물을 위한 퍼블리싱 백엔드입니다. 이는 상태 비저장(stateless) Streamable HTTP MCP 서버입니다. Claude는 사고(주제 선택, 문구, 캡션)를 담당하고, 이 서비스는 부수 효과(기록, 이미지 렌더링, 스토리지, Graph API)를 담당합니다. 이미지는 생성되지 않고 조판(typeset)됩니다: 포스터의 텍스트는 입력된 텍스트와 정확히 동일합니다.

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

도구

도구

용도

get_past_topics()

최신순 기록, 최대 200행: {id, topic, category, status, created_at}. 주제를 선택하기 전에 읽으세요.

reserve_topic(topic, category)

status='reserved'를 삽입하고 {id}를 반환합니다. categorynews, subject, career 중 하나입니다. 반복된 주제는 duplicate_topic으로 실패합니다.

render_post({template, headline, points, footer?, eyebrow?, slug?})

브랜드 HTML 템플릿을 정확한 크기의 sRGB JPEG로 렌더링하여 업로드하고, 이미지 블록과 공개 URL을 반환합니다.

publish_socials(image_url, caption, hashtags, history_id)

Instagram과 Facebook에 게시하고 결과를 기록합니다.

mark_draft(history_id, image_url, caption)

섀도우 모드: 게시하지 않고 완성된 게시물을 draft로 기록합니다.

check_token()

Meta 토큰이 만료되기까지 남은 일수 + 부여된 스코프.

모든 도구는 JSON을 반환합니다. 성공은 {"ok": true, ...}이고, 실패는 {"ok": false, "error": {code, message, retryable, details}}를 포함하는 MCP 오류 결과입니다. 호출자에게 원시 스택 트레이스가 던져지지 않습니다.

render_post

templatenews, subject, career 중 하나입니다 — posts 테이블이 사용하는 동일한 세 가지 카테고리입니다.

필드

제한

참고

headline

60자

Barlow 700, 최대 3줄. 문장형 대소문자(sentence case), 제목형 대소문자(title case) 아님.

points

3~4개 항목, 각 90자

Barlow 400, 각 항목에 금색 마커 하나

eyebrow

32자, 선택 사항

금색, CSS로 대문자 처리, 예: NAVIGATION

footer

90자, 선택 사항

푸터 바 왼쪽, 예: DGCA · 14 Aug 2026

두 개의 콘텐츠 블록을 반환합니다: 이미지 블록(base64 JPEG)과 공개 URL, 파일명, 치수, 바이트 크기, 렌더링 시간이 포함된 텍스트 블록.

업로드 전에 두 가지 가드가 실행되며, 둘 다 문제가 되는 필드를 지목합니다:

  1. 길이 제한 — Chromium을 건드리기 전에 확인하는 저비용 거부입니다. points[2] is 97 characters, limit is 90. Shorten it and retry.

  2. 페이지 내 측정 — 레이아웃 이후에 실행됩니다. 모든 텍스트 박스는 고정 크기의 클리핑 박스이며, 내용이 박스보다 높거나 넓으면 필드 이름과 픽셀 단위 오버플로와 함께 렌더링이 거부됩니다. 이는 문자 수로는 발견할 수 없는 문제, 예를 들어 길이는 허용되지만 가장자리를 벗어나는 줄바꿈 불가능한 80자 토큰 같은 경우를 잡아냅니다.

어느 가드가 발생해도 아무것도 업로드되지 않으므로 거부는 1초면 끝나고, 해결 방법은 항상 "지정된 필드를 줄이는 것"입니다.

이미지 블록은 여전히 반환되므로 문구를 맥락에서 읽을 수 있지만, 더 이상 정확성 검증 수단은 아닙니다. 고정 템플릿은 단어 철자를 틀리거나 도표를 만들어 내지 않기 때문입니다. 최악의 허용 입력(58자 헤드라인 + 90자 포인트 4개, 가장 넓은 허용 eyebrow와 footer)은 세 템플릿 모두에 맞는 것으로 검증되었습니다.

publish_socials, 단계별

  1. image_url에 대해 HEAD 요청을 보내고 200 + content-type: image/jpeg를 확인합니다. Meta는 이 URL을 서버 측에서 가져오며, 잘못된 URL이 있으면 몇 시간 후에 불투명하게 실패합니다. (HEAD를 거부하는 저장소에는 대신 1바이트 범위 GET을 사용합니다.)

  2. Instagramfull_caption = caption + "\n\n" + hashtags.join(' '), 2200자로 제한됩니다. 끝에서부터 해시태그만 제거되며 캡션 본문은 절대 잘리지 않습니다. 본문만 2200자를 초과하면 아무것도 게시되기 전에 caption_too_long으로 호출이 실패합니다. POST {IG_USER_ID}/mediaGET {container}?fields=status_code,status를 최대 60초 동안 1초에 한 번 폴링 → FINISHED일 때만 게시합니다. ERROR인 경우 status 문자열이 그대로 반환됩니다. Meta가 무엇을 싫어했는지 설명하는 유일한 곳이기 때문입니다.

  3. Facebookurlmessage를 사용하여 POST {FB_PAGE_ID}/photos. Instagram 결과와 관계없이 시도됩니다.

  4. 기록posts 행에 ig_post_id, fb_post_id, image_url이 기록되고 statuspublished(둘 다 성공), partial(하나만 성공), failed(둘 다 실패)로 설정됩니다.

{ig_post_id, fb_post_id, status, errors: [...]}를 반환합니다. 단일 플랫폼 실패는 절대 무시되지 않습니다. 실패한 플랫폼, 실패 단계, Meta 자체의 code / error_subcode / message와 함께 errors[]에 나타납니다.

Related MCP server: Social Analytics MCP Server

환경 변수

변수

필수 여부

설명

MCP_AUTH_TOKEN

/mcp용 공유 비밀키입니다. 커넥터는 Authorization: Bearer <value>를 보내야 합니다. 설정하지 않으면 서버는 여전히 부팅되어 /health를 제공하지만 모든 /mcp 요청을 500으로 거부합니다 — 실패 시 닫히며(fail closed), 절대 열리지 않습니다. 다음 명령으로 생성하세요: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))".

SUPABASE_URL

https://<project-ref>.supabase.co.

SUPABASE_SERVICE_KEY

서비스 역할(Service-role) 키입니다. RLS를 우회합니다 — 서버 측 전용입니다. 커넥터 구성에 절대 넣지 마세요.

META_GRAPH_VERSION

아니요(기본값 v23.0)

모든 호출에 사용되는 Graph API 버전입니다.

IG_USER_ID

게시용

Instagram 비즈니스 계정 ID(숫자, @핸들이 아님).

FB_PAGE_ID

게시용

해당 Instagram 계정에 연결된 Facebook Page ID.

META_PAGE_ACCESS_TOKEN

게시용

instagram_basic, instagram_content_publish, pages_show_list, pages_read_engagement, pages_manage_posts 권한을 가진 장기(long-lived) Page 액세스 토큰입니다. 약 60일 후 만료됩니다 — check_token()이 남은 시간을 알려줍니다.

PORT

아니요

Render가 설정합니다. 기본값 10000.

MAX_INLINE_IMAGE_BYTES

아니요(기본값 1400000)

이 크기를 초과하면 인라인 base64 미리보기가 축소됩니다.

CHROMIUM_EXECUTABLE_PATH

아니요

Chrome/Chromium 바이너리의 명시적 경로입니다. 플랫폼별 기본값을 재정의합니다.

CHROMIUM_SINGLE_PROCESS

아니요

--single-process를 강제하려면 1로 설정합니다. 브라우저 재사용을 포기합니다 — 실행당 렌더링 한 번입니다. 브라우저 수명 주기를 참조하세요.

로컬 실행을 위해 .env.example.env로 복사하세요. .env는 gitignore되어 있습니다 — 그 상태를 유지하세요.

설정

1. Supabase

SQL 편집기에서 migrations/001_init.sql을 실행하세요(또는 supabase db push). 이 스크립트는 멱등적이며 다음을 생성합니다:

  • 체크 제약 조건과 topic에 대한 고유 인덱스를 가진 posts 테이블 — 이 인덱스가 중복 방지 장치이므로 중복 예약은 실패하도록 되어 있습니다.

  • created_at descstatus 인덱스.

  • 정책이 없는 RLS가 활성화된 posts (서비스 키만 접근 가능).

  • public post-images 스토리지 버킷과 공개 읽기 정책. 공개 읽기는 필수입니다: Meta가 JPEG를 직접 가져오며 자격 증명을 제시할 수 없기 때문입니다.

2. Meta

Facebook Page에 연결된 Instagram 비즈니스(Business) 또는 크리에이터(Creator) 계정과 위에 나열된 스코프를 가진 장기(long-lived) Page 토큰이 필요합니다. 첫 실행 전에 check_token()으로 확인하세요 — 만료된 토큰은 아침 게시가 실패하는 가장 흔한 원인입니다.

3. Render에 배포

render.yaml 사용(Blueprint):

  1. 이 저장소를 GitHub에 푸시하세요.

  2. Render 대시보드 → NewBlueprint → 저장소를 선택하세요. render.yaml을 읽습니다: Node 20, npm ci && npm run build, npm start, /health 상태 확인.

  3. Render가 모든 sync: false 변수를 입력하라고 요청합니다. 붙여넣으세요.

  4. 배포한 다음 로그에서 server.listening ... auth=configured를 확인하세요. auth=MISSINGMCP_AUTH_TOKEN이 설정되지 않았다는 뜻입니다.

수동으로:

  1. 새로 만들기Web Service → 저장소를 연결하세요.

  2. 런타임 Node, 빌드 npm ci && npm run build, 시작 npm start.

  3. 헬스 체크 경로 /health.

  4. 위 표의 환경 변수에 더해 NODE_VERSION=20도 추가하세요.

확인:

curl https://<your-service>.onrender.com/health
# {"status":"ok","server":{...},"tools":[...six...],"uptime_s":3}

무료가 아닌 Starter 플랜을 사용하세요. Chromium은 Node에 더해 약 400MB의 상주 메모리가 필요하며, 무료 인스턴스는 512MB라서 렌더링 중간에 OOM이 발생합니다. 그러면 실패가 유용한 로그 한 줄로 나타나지 않고 죽은 워커로만 보입니다. 무료 플랜은 또한 비활성 상태가 되면 절전 모드로 들어가기 때문에, 하루 첫 도구 호출은 추가로 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 (프로젝트 범위, 토큰 없이 커밋됨):

{
  "mcpServers": {
    "weone-social": {
      "type": "http",
      "url": "https://<your-service>.onrender.com/mcp",
      "headers": { "Authorization": "Bearer ${MCP_AUTH_TOKEN}" }
    }
  }
}

Claude 데스크톱/웹의 사용자 지정 커넥터 대화상자에서 같은 /mcp URL을 붙여넣고 요청 헤더 필드에 bearer 토큰을 넣으세요. 인증은 설계상 헤더 전용입니다. 토큰은 절대 쿼리 매개변수로 받지 않는데, 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 start

npm 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_URLSUPABASE_SERVICE_KEY만 필요하며, --no-upload는 아무것도 필요로 하지 않습니다.

로컬 파일은 업로드 전에 쓰이므로 Supabase가 실패해도 확인할 것이 남습니다.

렌더링 파이프라인

헤드리스 Chromium이 file://src/templates/{template}.html을 불러오고, 값이 DOM에 쓰인 다음 페이지가 스크린샷으로 캡처됩니다. 같은 입력은 항상 같은 픽셀을 만들어냅니다.

  • 템플릿src/templates/에 있습니다. tokens.css는 모든 색상을 담고, base.css는 세 템플릿이 공유하는 뼈대를 담습니다. 템플릿 파일은 형제 파일과 오직 eyebrow 처리와 포인트 마커만 다릅니다. (news: 금색 라인, subject: 번호가 매겨진 금색 원, career: 금색 셰브론.)

  • 글꼴은 자체 호스팅됩니다: src/templates/fonts/ (모든 텍스트에 Barlow 400/600/700, 워드마크에만 Cinzel 600, 라틴 서브셋, OFL). 렌더링 시점에는 아무것도 가져오지 않습니다. 네트워크 호출은 출력을 비결정적으로 만들고 Render에서 조용히 실패해 시스템 세리프로 폴백되기 때문입니다. 렌더러는 document.fonts.ready를 기다린 뒤 폴백을 스크린샷하지 않고 실제로 두 폰트가 로드되었는지 단언합니다.

  • 사용자 텍스트가 마크업에 이어붙여지는 일은 없습니다. 값은 textContentcreateElement로 들어가므로 이스케이프를 잘못할 일이 없습니다. 헤드라인의 <script>는 문자 그대로 포스터에 표시됩니다.

  • 뷰포트deviceScaleFactor: 2에서 1080×1350이므로 스크린샷은 2160×2700이 되고 다운샘플링됩니다. 텍스트 가장자리가 깨끗하게 유지됩니다.

  • sharp: resize(1080, 1350, {fit:'cover'})toColorspace('srgb')jpeg({quality: 90, chromaSubsampling: '4:4:4'}), 메타데이터 제거. 4:4:4는 장식이 아닙니다. 4:2:0은 컬러 텍스트 가장자리를 번지게 하는데, 이 포스터는 텍스트이기 때문입니다.

  • 인코딩된 JPEG가 8MB 미만이고 디코딩된 치수가 실제로 요청한 값인지 단언합니다.

  • {yyyy-mm-dd}-{slug}-{6 hex}.jpg(UTC 날짜)로 업로드됩니다. 모든 렌더링은 고유한 키를 가지며 어떤 것도 덮어쓰지 않습니다upsert: false. 주제를 다시 렌더링해도 이미 이전 URL을 게시한 게시물의 이미지는 바뀌지 않습니다. cacheControl이 60초인 것도 같은 이유입니다. 잘못된 객체가 1년 동안 CDN에 고정되는 대신 1분 안에 수정될 수 있기 때문입니다. Meta는 업로드 직후 서버 측에서 URL을 한 번 가져오므로 긴 캐시가 필요하지 않습니다. 객체는 계속 쌓이며, 스토리지는 라이브 게시물의 오래된 이미지보다 훨씬 저렴합니다.

레이아웃 동작

타이포그래피는 포인트 수에 따라 크기가 조정됩니다. 포인트가 3개면 헤드라인 68px, 본문 36px, 4개면 60px, 32px입니다. 이는 CSS에서 :has()로 처리되므로 레이아웃 결정은 전적으로 템플릿에 있으며 렌더러는 알지도 신경 쓰지도 않습니다. 오버플로 가드는 스케일링 후 실행되므로 측정 대상은 스케일링된 결과입니다.

콘텐츠 블록은 헤더 라인과 푸터 바 사이에서 세로로 중앙 정렬됩니다. 텍스트 양이 달라지므로 고정 간격만으로는 채움 목표를 유지할 수 없습니다. 그래서 세 개의 탄성 요소가 남은 공간을 나눠 갖습니다: 위쪽 밴드, 아래쪽 밴드, 헤드라인 아래 간격. 밴드는 150px로 상한이 정해져 있으며, 이것이 "큰 빈 여백 없음"을 강제합니다. 상한에 도달하면 남는 공간은 헤드라인 간격으로 들어가 구멍이 아니라 여유 공간으로 읽힙니다.

대표 콘텐츠에서 측정된 세로 채움률: 74–79%, 밴드 99–124px. 의도적으로 빈약한 경우(한 줄 헤드라인, 한 줄 포인트 3개)는 밴드가 150px 상한일 때 68.8%입니다. 밴드에 하드 캡이 있으면 그 적은 텍스트에 대해 이것이 산술적 최대치이며, 더 올리려면 포인트들이 리스트로 읽히지 않을 만큼 멀리 벌어져야 합니다.

헤드라인은 sentence case의 Barlow 700, 행간 1.1, 자간 −0.5px입니다. Cinzel은 "WE ONE AVIATION" 워드마크에만 남아 있습니다. sentence case는 코드에서 강제되지 않습니다. 헤드라인을 기계적으로 소문자로 바꾸면 DGCA, ATPL, AAI가 망가지기 때문입니다. 대신 headline 필드 설명에 명시되어 있습니다.

각 템플릿은 인라인으로 평면 SVG 악센트를 가집니다: 넓은 대각선 라인(news), 동심 나침반 호(subject), 상승하는 셰브론 스택(career). 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이 프로세스 수명 동안 공유되며, 연결이 끊길 때만 다시 시작됩니다. 실행에는 약 1초와 수백 MB가 소요되어 게시물마다 반복하기에는 너무 큽니다.

렌더 메모리. Chromium은 Node에 더해 약 400MB의 상주 메모리가 필요합니다. 무료 인스턴스는 512MB이며 그 환경에서는 OOM이 발생합니다. 배포가 렌더링 중간에 유용한 로그 없이 죽습니다. Starter 플랜을 사용하세요. 무료를 유지해야 한다면 재시작을 예상하고, 재시작 후 첫 렌더링을 콜드 스타트로 취급하세요.

바이너리가 어디서 오는지는 호스트에 따라 다릅니다:

호스트

소스

CHROMIUM_EXECUTABLE_PATH 설정

그 경로가 항상 우선합니다.

Linux (Render)

@sparticuz/chromium, 자체 바이너리를 포함하므로 빌드 시 브라우저 다운로드가 없습니다.

macOS / dev

playwright-core가 이미 캐시해 둔 것 (npx playwright-core install chromium)

--single-process는 의도적으로 사용하지 않습니다. 이 플래그는 하나의 브라우저 재사용과 호환되지 않습니다. 이 플래그에서 BrowserContext를 닫으면 브라우저 전체가 내려가므로 두 번째 렌더링은 "Target page, context or browser has been closed" 오류로 실패합니다. 이 코드베이스에서 측정한 결과: 이 플래그를 쓰면 컨텍스트 3개 중 1개만 살아남고, 쓰지 않으면 3개 모두 살아남습니다. 재사용이 트레이드오프에서 더 가치 있는 절반입니다. 어떤 호스트가 요구한다면 CHROMIUM_SINGLE_PROCESS=1을 설정해 다시 강제할 수 있으며, 실행당 렌더링은 한 번이라고 기대하세요.

오류 처리

코드

의미

bad_input

인수 검증에 실패했습니다.

duplicate_topic

주제가 이미 존재합니다. 설계대로 동작한 것입니다. 다른 주제를 선택하세요.

not_found

해당 history_id에 대한 posts 행이 없습니다. reserve_topic을 호출했나요?

db_error / storage_error

Supabase가 거부했습니다. details에 Postgres 코드가 들어 있습니다.

image_generation_failed / image_too_large

공급자 또는 sharp 문제.

image_url_unreachable

Meta가 가져올 URL이 접근 가능한 JPEG가 아닙니다.

caption_too_long

캡션 본문만으로 2200자를 초과합니다. 해시태그는 자동으로 잘리지만 본문은 절대 잘리지 않습니다.

meta_error

Graph API. detailscode, error_subcode, type, fbtrace_id가 변경 없이 들어 있습니다.

timeout

무언가가 예산을 초과했습니다. (이미지 60초, 컨테이너 폴링 60초, Graph 30초)

config_error

필수 환경 변수가 누락되었습니다. retryable: false.

Meta 코드 190과 200은 절대 재시도되지 않습니다. 190은 만료되었거나 유효하지 않은 토큰, 200은 권한 누락입니다. 둘 다 사람이 처리해야 하며, 재시도는 실제 원인을 숨긴 채 rate limit만 소모합니다. 이러한 오류는 retryable: false와 무엇을 해야 하는지 설명하는 needs_human 메모와 함께 반환됩니다.

모든 도구 호출은 tool.starttool.ok/tool.error를 기간과 함께 기록하고, 모든 Graph 호출은 graph.call을 메서드, 엔드포인트, 상태, 경과 시간(ms)과 함께 기록합니다. 따라서 Render의 로그 뷰어만으로도 실행을 재구성할 수 있습니다.

문제 해결

증상

원인

모든 요청에서 401

헤더 누락, 또는 토큰이 MCP_AUTH_TOKEN과 일치하지 않음.

/mcp에서 500 config_error, /health는 정상

서비스에 MCP_AUTH_TOKEN이 설정되지 않음.

image_url_unreachable

post-images 버킷이 공개되지 않음, 또는 업로드가 조용히 실패함. npm run smoke 실행.

IG 컨테이너가 60초 동안 IN_PROGRESS에 머무름

Meta가 이미지를 가져올 수 없거나 느림. 먼저 브라우저에서 URL을 확인.

meta_error 코드 190

토큰 만료. 새로운 장기 Page 토큰을 발급. check_token()이 7일 이내에서 경고.

meta_error 코드 200

스코프 누락 — instagram_content_publish / pages_manage_posts 재부여.

duplicate_topic

버그가 아님. 주제가 이미 posts에 있음.

하루 첫 호출이 타임아웃

무료 플랜 콜드 스타트. 먼저 /health 호출, 또는 starter로 이동.

렌더링 중 워커가 오류 없이 종료됨

메모리 부족. Chromium은 ~400MB 필요, 무료 인스턴스는 512MB. starter로 이동.

필드명을 지정하는 bad_input

의도된 동작. 지정된 필드 이름을 줄이고 render_post 다시 호출 — 아무것도 업로드되지 않음.

Self-hosted fonts failed to load

src/templates/fonts/dist/에 도달하지 못함. npm run build 다시 실행; 렌더링은 거부되며 대체 serif로 제공되지 않음.

Target page, context or browser has been closed

CHROMIUM_SINGLE_PROCESS=1이 설정됨. 이 플래그는 실행당 한 번만 렌더링 허용 — 해제.

Render에서 템플릿 파일을 찾을 수 없음

npm run build를 건너뛰어 dist/templates/가 없음.

레이아웃

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.yaml
F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    A
    quality
    C
    maintenance
    Enables AI assistants to manage Instagram and Threads accounts — publish content, handle comments, view insights, search hashtags, and manage DMs through the Meta Graph API.
    59
    46
    10
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    MCP 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.
    4
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server for automatic Instagram publishing — single image, carousel and Reels — via the official Instagram Graph API.
    40

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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