Hello MCP Server
Provides tools for managing Facebook Pages, including posting, uploading photos, deleting posts, viewing insights, and managing comments.
Provides access to Google services including Google Business Profile, YouTube, Google Ads, and Google Analytics (GA4).
Provides tools for managing Google Ads accounts and campaigns, including listing accounts/campaigns, viewing performance metrics, keyword data, and running GAQL queries.
Provides tools for querying Google Analytics 4 data, including traffic overviews, top pages, realtime users, and custom reports.
Provides tools for interacting with Instagram via the Graph API, including profile management, media publishing, post insights, and comment management for Business/Creator accounts.
Provides social media management for Meta platforms including Instagram and Facebook via their Graph APIs.
Provides tools for sending WhatsApp messages, templates, media, reactions, and managing business profile and templates via the Cloud API.
Provides tools for interacting with YouTube channels and videos, including searching, listing videos, viewing comments, replying to comments, and updating video metadata.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Hello MCP Serversay hello"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Social MCP Portal (Cloudflare Workers)
MCP servers for Instagram, Facebook Pages, X (Twitter), WhatsApp, Google Business Profile, YouTube, Google Ads and Google Analytics (GA4), running as a single stateless Worker via createMcpHandler from the Agents SDK.
Endpoints
Endpoint | Server | Tools |
| Social MCP Portal | every tool below, in one endpoint |
| Instagram MCP Server |
|
| Facebook MCP Server |
|
| X (Twitter) MCP Server |
|
| WhatsApp MCP Server |
|
| Google MCP Server | all four Google servers below |
| Google Business Profile MCP Server |
|
| YouTube MCP Server |
|
| Google Ads MCP Server |
|
| Google Analytics MCP Server |
|
| Tenant API Gateway | sintetizadas do manifesto do tenant |
Every endpoint also exposes ping, which reports which credentials are configured — handy for checking the deploy without touching the social APIs.
Related MCP server: createMcpHandler MCP Server
Tenant API Gateway (/mcp/tenant)
Os outros endpoints têm ferramentas fixas no código. Este não: as ferramentas são
sintetizadas do manifesto do tenant que está chamando, resolvido pelo token da
requisição. Ele existe para a plataforma de agentes de WhatsApp
(celfons/whatsapp, issue #1324) consultar a API própria de cada cliente ao vivo,
dentro do turno, sem que o backend dela mude uma linha: para o backend, isto
aqui é um servidor MCP como qualquer outro.
Como funciona
turno → backend lê tenant_mcp_servers (URL = este endpoint)
→ tools/list (sintetizado do manifesto)
→ o LLM escolhe a ferramenta; bindCallScope injeta o telefone verificado
→ tools/call → este gateway chama a API REST do cliente
→ projeta SÓ os campos declarados
→ o texto entra no prompt como <external_data>O manifesto
Guardado em KV (TENANT_MANIFESTS), em duas famílias de chave:
tenant-token:<sha-256 do token> -> tenantId
tenant-manifest:<tenantId> -> o manifestoO índice guarda o hash do token, não o token: um dump do KV não vira um chaveiro.
{
"tenantId": "tnt_1",
"label": "ERP da Loja",
"baseUrl": "https://api.cliente.com",
"auth": { "type": "bearer", "token": "..." },
"timeoutMs": 3000,
"tools": [
{
"name": "consultar_pedido",
"description": "Status e previsão de entrega de um pedido",
"method": "GET",
"path": "/pedidos/{orderId}",
"scope": "customer",
"identityParam": "telefone",
"params": [
{ "name": "orderId", "in": "path", "required": true },
{ "name": "telefone", "in": "query", "required": true }
],
"fields": [
{ "path": "status", "label": "Status" },
{ "path": "entrega.previsao", "label": "Previsão" }
],
"maxChars": 1200
}
]
}Onde este Worker atende — confira ANTES de cadastrar
O host que você usa tem de ser um que chegue a este Worker. Isso não é
subentendido: mcp.closing.trade não é este Worker — é um portal MCP protegido por
OAuth, e ele responde 401 invalid_token em /mcp* e 404 em todo o resto, sem nunca
encostar aqui. Cadastrar contra ele falha em silêncio duas vezes: a rota admin dá 404, e
a plataforma, que manda Authorization: Bearer <token do tenant>, é recusada pelo portal
como token OAuth inválido.
Descubra o host certo no dashboard: Workers & Pages → mcp-social → Settings →
Domains & Routes. Sem routes no wrangler.jsonc e sem workers_dev: false, existe
uma URL mcp-social.<subdomínio-da-conta>.workers.dev que já funciona hoje. Para um host
bonito, adicione um Custom Domain ao Worker (ex.: gateway.closing.trade) — não
tente rotear um subpath de mcp.closing.trade, que já pertence ao portal.
Teste de um comando ($GATEWAY = o host candidato):
curl -s -o /dev/null -w "%{http_code} %{content_type}\n" "$GATEWAY/mcp/naoexiste"404 application/json→ chegou no Worker. É este host.401comWWW-Authenticate: Bearer realm="OAuth"→ é o portal, não o Worker.404 text/plain→ não chegou em Worker nenhum.
Nas duas superfícies, use o MESMO host: a rota /admin/* e a URL que a plataforma
guarda em tenant_mcp_servers são o mesmo Worker.
Cadastrar:
O namespace já existe e o binding está declarado em wrangler.jsonc.
Cadastro pela rota admin (preferido)
PUT /admin/tenants/:tenantId/manifest, protegida por ADMIN_TOKEN
(npx wrangler secret put ADMIN_TOKEN). Ela valida pelo mesmo parseManifest
antes de gravar e grava as duas chaves de uma vez:
curl -X PUT $GATEWAY/admin/tenants/tnt_1/manifest \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "manifest": { ... }, "token": "<token-que-a-plataforma-vai-usar>" }'Manifesto inválido é recusado com motivo nomeado e nada é gravado — diferente
do painel, que aceita JSON quebrado calado e deixa a falha aparecer depois, como um
agente respondendo sem o dado. GET devolve o manifesto sem a credencial da API
do cliente; DELETE torna o tenant inalcançável.
Sem ADMIN_TOKEN configurado, /admin/* responde 503 — ausência de credencial nunca
vira "aberto".
Cadastro pela CLI (emergência)
npx wrangler kv key put --binding=TENANT_MANIFESTS "tenant-manifest:tnt_1" --path manifesto.json
npx wrangler kv key put --binding=TENANT_MANIFESTS "tenant-token:<sha256-do-token>" "tnt_1"Do lado da plataforma, a linha em tenant_mcp_servers aponta para
$GATEWAY/mcp/tenant com Authorization: Bearer <token>, e o tool_policy
classifica cada ferramenta com o MESMO escopo declarado aqui.
O que o manifesto obriga, e por quê
fieldsé obrigatório. Sem projeção, o JSON do cliente iria cru para o prompt — com margem, custo interno e dado de terceiro dentro. Campo não declarado não viaja.Lista: use o caminho de repetição
produtos[].nome. Ele projeta todos os itens (até 30, ou até o teto de caracteres), uma linha por item com os campos daquele item juntos — é o que permite uma ferramenta de BUSCA e o agente sugerir a partir do catálogo do cliente. Agrupar por campo em vez de por item deixaria o modelo parear preço com o produto errado, que é a pior saída possível deste módulo."fields": [ { "path": "produtos[].nome", "label": "Produto" }, { "path": "produtos[].preco", "label": "Preço" } ]→
Produto: Creatina · Preço: R$ 89Ferramenta
customerprecisa declarar oidentityParamentre osparams. O backend recusa (identityParam_missing) a que não o declara noinputSchema; aceitar aqui seria anunciar uma ferramenta nunca chamável.Ferramenta
businessnão pode teridentityParam— a contradição é recusada, não resolvida em silêncio.Manifesto inválido é recusado inteiro: meio manifesto aplicado é uma ferramenta que some sem ninguém notar.
Quando a API do cliente não fala a língua da plataforma
Quatro campos opcionais existem porque, sem eles, boa parte das APIs reais fica inexprimível — não por elegância.
transformno parâmetro ("digits"·"br_local"). A plataforma escreve noidentityParamowa_idda Meta: E.164 em dígitos, com DDI (5534999530186). ERP brasileiro guarda o telefone em formato local. Sem normalizar, a busca casa zero registros — e nada quebra: o leg degrada emempty_resulte o dono conclui que "não funciona" sem nenhum sinal apontando a causa.br_localremove o DDI só quando o número tem 12–13 dígitos e começa em55(a janela é o que impede um número de DDD 55 de perder dois dígitos).query— pares fixos, do autor do manifesto, fora do alcance do modelo:{"idBranch": "7", "active": "true", "take": "20"}. Pinar a unidade como parâmetro seria deixar o modelo escolher a unidade a partir do texto do cliente.root— onde, na resposta, mora o que a projeção deve ler, em candidatos.["list", "lista", "$"]desce num envelope de paginação;["[0]", "$"]cobre objeto-ou-lista quando o swagger do cliente diz uma coisa e a API faz outra."$"é a resposta inteira. Candidato que existe mas está vazio não conta como achado.resolve— o saltoidentidade verificada → chave interna, feito pelo gateway antes da consulta principal:"resolve": { "path": "/api/v1/members/basic", "param": "phone", "query": { "take": "1" }, "extract": ["[0].idMember", "idMember"], "into": "memberId" }Ele existe porque a plataforma só conhece o telefone (é o que ela verifica no ingresso do canal) e a maioria das APIs keya por um id interno. Sem o salto, metade das consultas escopadas por cliente é inexprimível.
O ADR-0036 proíbe encadear ferramentas — mas o que ele proíbe é o laço de decisão da LLM (chamar → ler → decidir de novo). Daqui sai um
tools/call; os dois saltos são determinísticos e dividem o mesmotimeoutMs. E a amarra fica mais forte: o limite conhecido nº 1 do ADR-0036 é que a plataforma prova o envio da identidade, não o respeito a ela — com o salto, a chave é derivada do telefone verificado pelo próprio gateway, e o modelo não tem como propô-la.Três propriedades não são configuráveis, de propósito: o valor enviado é sempre o
identityParam(se houvesse campo para escolher outro, o modelo escolheria);intonão pode colidir com parâmetro nem com query fixa; e resolução vazia aborta a ferramenta — a consulta principal não acontece, porque um/receivablessemmemberIddevolveria o financeiro da academia inteira.
O que este gateway assume como responsabilidade
Ao viver fora da plataforma, ele perde o INV-TENANT-SCOPE e o guard estrutural que o
cobra lá. O que sobra é a regra em src/tenant/store.ts e os testes: o token resolve
UM tenant, e as ferramentas daquela requisição saem do manifesto DAQUELE tenant. Não
há caminho que leia manifesto de outro, e não há listagem.
Também moram aqui: o SSRF (https apenas, sem faixa privada/loopback/metadata,
redirecionamento não seguido, corpo com teto de bytes), a credencial da API do cliente,
e o cuidado de a mensagem de erro nunca ecoar o corpo do cliente — esse texto entra
no prompt de um agente.
Limite conhecido: um host que resolve para IP privado passa pela guarda (não há
resolução de DNS antes do fetch). Rebind de DNS exigiria proxy com resolvedor próprio;
está fora de escopo, e é melhor estar escrito do que subentendido.
Preset: EVO (academias)
A EVO (W12) é o sistema de gestão que boa parte das academias usa. Ela é o oposto do caso que o manifesto por tenant atende: a API é fixa e conhecida, a mesma para todas as academias. Colar 250 linhas de JSON por academia produziria divergência entre clientes idênticos e nenhum lugar onde corrigir todo mundo quando a W12 mudar um campo.
src/tenant/presets/evo.ts é esse lugar. Ele gera um manifesto e o passa pelo mesmo
parseManifest — não é um caminho de gravação paralelo, é um gerador de entrada para o
mesmo. Ativar uma academia vira três campos.
Ativar
1 · No gateway (aqui):
curl -X PUT $GATEWAY/admin/tenants/tnt_gym/preset/evo \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dns": "<dns-da-unidade-na-evo>",
"secretKey": "<secret-key-da-evo>",
"idBranch": 7,
"label": "Academia Centro",
"include": "business",
"token": "<token-que-a-plataforma-vai-apresentar>"
}'Auth da EVO é Basic (DNS como usuário, secret key como senha), montado aqui — o
esquema do manifesto não precisa saber o que é Basic. A resposta traz a tool_policy
pronta e nunca a credencial.
include: só preço e catálogo, ou tudo
"include": "business" no corpo anuncia só as cinco consultas sobre o negócio
(preço, plano, grade, modalidades, unidade) e deixa de fora as quatro sobre o aluno.
É a ativação que a maioria das academias vai querer primeiro, e ela tem uma
consequência que não é cosmética: o telefone do cliente nunca sai do perímetro.
Some com ele o dever de aviso do ADR-0036 — não porque foi dispensado, mas porque o
fato que o originava deixa de acontecer.
Dava para chegar perto disso só pela tool_policy do lado da plataforma (consulta sem
regra é inchamável, e o modelo nem a vê). Mas ela seguiria sendo anunciada e recusada
a cada turno, somando em scope_refused/unclassified — a métrica que existe para gritar
"o dono esqueceu de classificar" passaria a gritar num estado intencional. Alerta falso é
o que ensina a ignorar alerta, então o desligamento acontece onde a consulta nasce.
Valor desconhecido é recusado, nunca lido como o default: um "buisness" com erro de
digitação tratado como "all" ligaria as consultas sobre o aluno numa academia que pediu
para não tê-las. Sem o campo, tudo é anunciado — o recorte é opt-in.
A tool_policy da resposta é derivada do manifesto que acabou de ser gravado, então
ela acompanha o recorte sozinha e nunca descreve uma consulta que aquele tenant não
anuncia.
2 · Na plataforma (celfons/whatsapp), cole a tool_policy que veio na resposta —
POST, e a chave vai em x-admin-key:
curl -X POST https://<plataforma>/api/admin/tenants/tnt_gym/mcp-server \
-H "x-admin-key: $ADMIN_API_KEY" -H "Content-Type: application/json" \
-d '{ "label": "EVO",
"url": "'$GATEWAY'/mcp/tenant",
"authHeader": "Bearer <o-mesmo-token-acima>",
"toolPolicy": { "tools": { ... } } }'A resposta ecoa o que foi classificado (classifiedTools, allAnnounced) — confira
que o número bate com o que você mandou.
Os dois documentos têm de concordar: uma ferramenta anunciada aqui e não classificada
lá nasce inchamável (unclassified), e o dono só descobre pela métrica de degradação. Por
isso a policy é gerada, não transcrita — e no dialeto da borda da plataforma
(identityParam camelCase, sem version), não no do banco dela, senão precisaria de
tradução manual e a promessa de "colável" seria falsa.
As ferramentas
Ferramenta | Escopo | Endpoint EVO | Salto |
| customer |
| — |
| customer |
| — |
| customer |
| → |
| customer |
| → |
| customer |
| → |
| customer |
| → |
| business |
| — |
| business |
| — |
Com "include": "business", só as duas últimas são anunciadas.
evo_meu_plano não precisa de salto porque MembersBasicApiViewModel.memberships já vem
embutido na busca por telefone — uma consulta, e é o que a mantém barata.
O preset usa /api/v1/members/basic, não /api/v2/members: o v2 devolve cpf,
document, address, zipCode, birthDate e photoUrl. Projeção estreita protege o
prompt, mas o que não sai da EVO não precisa de projeção — é uma camada de PII a menos
atravessando o fio.
A regra: duas classes, e não há terceira
(a) fala do cliente que perguntou, ou (b) é plano e preço. É uma allowlist, e o que está fora não está fora por ser perigoso — grade de aulas, modalidades, endereço, horário e convênios são todos inofensivos. Estão fora porque allowlist que aceita "isso também é inofensivo" deixa de ser allowlist em três meses. E porque quase tudo dessa lista é quase estático: mora mais barato no contexto que o dono escreve no portal do agente, sem custar uma consulta viva e uma chamada de LLM por turno.
tests/evoPreset.test.ts codifica a regra: consulta business cujo caminho não está na
lista de preço quebra o CI.
Por que não "todo GET"
A EVO tem ~80 rotas de leitura, e a tentação de expor todas é grande — leitura não estraga nada, certo? Não: o verbo não classifica nada. Quem aparece na resposta classifica. Três GETs da própria EVO bastam para mostrar:
GET /api/v1/members/resetPassworddevolve link de redefinição de senha. É leitura em HTTP e entrega a conta de alguém.GET /api/v1/receivables/debtorsé a lista de inadimplentes da academia inteira, com nome.GET /api/v1/pix/qr-codegera cobrança — efeito no mundo, verbo de leitura.
E há uma armadilha mais silenciosa: /api/v1/receivables, /api/v1/entries e
/api/v1/members/services respondem pela academia inteira quando o filtro de aluno
falta. São exatamente as que usam resolve, e é por isso que a resolução vazia
aborta em vez de seguir sem a chave.
Rota nova passa por duas perguntas, nesta ordem:
A resposta fala de uma pessoa amarrada ao telefone verificado, ou do negócio?
Se for do negócio — o dono poria isso na vitrine?
Custo negociado com parceiro, estoque, centro de custo, conta bancária e base de clientes
reprovam na segunda. tests/evoPreset.test.ts carrega o inventário fechado das rotas
proibidas: acrescentar uma delas quebra o CI.
O que o preset deliberadamente não faz
Escrita. Matricular em aula (POST /activities/schedule/enroll), criar prospect,
agendar experimental — todos existem na EVO e nenhum entra aqui. O ADR-0036 §2.3 contrata
leitura, e é essa contratação que dispensa reserva de idempotência: a entrega do turno
é at-least-once, então um turno reentregue repete a consulta. Repetir leitura custa
tempo; repetir matrícula cria duas. Escrita é outra feature, e ela reabre o P-3.
Verificar na ativação (não dá para saber do código)
Semântica do filtro
phoneem/members/basic: exato, parcial, ignora máscara? Se a academia guardar o número com DDI, o transform correto édigits— é uma linha emPHONE_TRANSFORM. É a suposição mais frágil do preset, e a que falha em silêncio: nenhuma consulta escopada acha ninguém, e nada fica vermelho. O sinal éempty_resultperto de 100% para aquele tenant, logo depois da ativação.Latência p95 de
/members/basic+/receivables: os dois saltos têm de caber nos3400 msdo preset, que ficam abaixo doMCP_TIMEOUT_MS(4 s) da plataforma.Rate limit da API por token — não está documentado pela W12, e o agente consulta por turno.
idBranchnuma rede multi-unidade: sem ele, a EVO responde pelo escopo do token.
O que não deve passar por aqui
Preço de plano é a pergunta nº 1 de uma academia e a mais tentadora de pôr no MCP. Mas o
ADR-0036 §2.4 proíbe cache entre turnos: evo_planos_e_precos seria chamado em todo
turno, com uma chamada de LLM de seleção junto, para um dado que muda uma vez por
semestre. Ele está aqui para quem quer o preço sempre vivo — mas contexto quase estático
(tabela de preços, horário, modalidades) sai mais barato no portal do agente ou no RAG,
que já são lastro e custam zero de latência. O MCP se paga no dado vivo e por pessoa:
situação do contrato, cobrança em aberto, aula agendada, vaga de hoje.
Tools
Instagram (Graph API — Business/Creator accounts)
Tool | What it does |
| Profile data (followers, bio, media count) |
| Recent posts |
| Details of a single post |
| Post metrics (reach, likes, saves, shares) |
| Publishes an image or reel (container + publish) |
| Comments on a post |
| Replies to a comment |
Facebook (Pages Graph API)
Tool | What it does |
| Pages the token can manage |
| Page details |
| Recent Page posts |
| Publishes a text post (optionally with a link) |
| Publishes a photo from a public URL |
| Deletes a post |
| Post metrics |
| Comments on a post |
| Replies to a comment |
X / Twitter (API v2)
Tool | What it does |
| Authenticated account |
| Profile lookup by @username |
| Recent posts from an account |
| A single post with its metrics |
| Search posts from the last 7 days |
| Publishes a post (reply/quote supported) |
| Deletes a post |
WhatsApp (Cloud API)
Tool | What it does |
| Free-form text (24-hour window only) |
| Approved template, with body parameters |
| Image, video, audio or document from a URL |
| Reacts to a message with an emoji |
| Marks a received message as read |
| Business profile of the sending number |
| Templates of a WABA, with approval status |
| Download URL of received media |
Google Business Profile
Tool | What it does |
| Manageable accounts |
| Locations of an account |
| Address, hours, phone, categories |
| Reviews with rating and existing replies |
| Replies to a review |
| Publishes a local post, with optional CTA |
YouTube (Data API v3)
Tool | What it does |
| Channel by ID, handle, or the authenticated one |
| Recent videos of a channel |
| Video with views, likes and comment count |
| Searches videos, channels or playlists |
| Comment threads on a video |
| Replies to a comment |
| Title, description, tags, privacy |
Google Ads
Tool | What it does |
| Accessible accounts |
| Campaigns with status, budget and metrics |
| Daily performance of one campaign |
| Ad groups of the account or a campaign |
| Keyword metrics, ordered by impressions |
| Arbitrary GAQL query |
| Pauses/enables/removes a campaign — changes live spend |
Google Analytics (GA4)
Tool | What it does |
| Analytics accounts |
| GA4 properties of an account |
| Report with the metrics/dimensions you choose |
| Users, sessions, engagement and conversions by channel |
| Most viewed pages |
| Users active right now |
| Metrics and dimensions available on the property |
Credentials
Store them as Worker secrets — never in wrangler.jsonc:
npx wrangler secret put FACEBOOK_ACCESS_TOKEN # Page token (Facebook, and Instagram fallback)
npx wrangler secret put INSTAGRAM_ACCESS_TOKEN # optional; overrides the token above for Instagram
npx wrangler secret put X_BEARER_TOKEN # X app-only token (reads)
npx wrangler secret put X_USER_ACCESS_TOKEN # X user-context token (posting/deleting)
# WhatsApp Cloud API
npx wrangler secret put WHATSAPP_ACCESS_TOKEN # optional; falls back to FACEBOOK_ACCESS_TOKEN
npx wrangler secret put WHATSAPP_PHONE_NUMBER_ID # default sender number ID
# Google (shared by Business Profile, YouTube, Ads and Analytics)
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET
npx wrangler secret put GOOGLE_REFRESH_TOKEN
# Google, per-product extras
npx wrangler secret put YOUTUBE_API_KEY # optional; used for read-only YouTube calls
npx wrangler secret put GOOGLE_ADS_DEVELOPER_TOKEN
npx wrangler secret put GOOGLE_ADS_LOGIN_CUSTOMER_ID # optional, for MCC accounts
npx wrangler secret put GOOGLE_ADS_CUSTOMER_ID # optional default account
npx wrangler secret put GA4_PROPERTY_ID # optional default propertyGoogle access tokens expire in about an hour, so the Worker mints them on demand from the refresh token and caches them in the isolate. A static GOOGLE_ACCESS_TOKEN is also accepted, which is convenient for local testing but will expire in production.
For local development, put the same keys in a .dev.vars file (git-ignored).
Required permissions:
Instagram:
instagram_basic,instagram_content_publish,instagram_manage_commentsFacebook:
pages_read_engagement,pages_manage_posts,pages_manage_engagementX:
tweet.read,users.read, andtweet.writefor publishingWhatsApp:
whatsapp_business_messaging,whatsapp_business_managementGoogle Business Profile:
business.manageYouTube:
youtube.force-ssl(writes); reads can use an API keyGoogle Ads:
adwords, plus an approved developer tokenGA4:
analytics.readonly
The Graph API version defaults to v21.0 and can be changed with the GRAPH_API_VERSION var in wrangler.jsonc.
Any tool called without its credential returns an MCP error result explaining which secret is missing — it never leaks the token value.
Running
npm install
npm start # http://localhost:5173 — built-in tool tester with an endpoint switcher
npm run build # generates dist/ (Worker + client assets)
npm run deploy # build + wrangler deployDeploying from Cloudflare Workers Builds
The Vite plugin is what fills in assets.directory — it writes the final Worker config to dist/mcp_social/wrangler.json at build time. So a bare npx wrangler deploy with no build first fails with:
✘ [ERROR] The `assets` property in your configuration is missing the required `directory` property.In the Workers Builds settings for this project, set either:
Build command:
npm run build(keeping the default deploy commandnpx wrangler deploy), orDeploy command:
npm run deploy(which builds and deploys in one step).
Connect an MCP client (Claude, MCP Inspector, …) to https://<your-worker>/mcp, or to one of the per-network endpoints.
Structure
src/
server.ts routing: one MCP server per endpoint
social/
shared.ts HTTP helper, error handling, secret loading
instagram.ts instagram_* tools
facebook.ts facebook_* tools
twitter.ts x_* tools
whatsapp.ts whatsapp_* tools
google-shared.ts Google OAuth: refresh token -> cached access token
google-business.ts google_business_* tools
youtube.ts youtube_* tools
google-ads.ts google_ads_* tools
google-analytics.ts ga4_* tools
env.d.ts secret/var types
tenant/
manifest.ts esquema do manifesto (a fronteira do gateway) + transforms
store.ts KV: token -> tenant -> manifesto (o isolamento mora aqui)
safeUrl.ts SSRF: destino verificado, sem redirect, corpo com teto
project.ts resposta do cliente -> só os campos declarados; raiz por candidatos
gateway.ts manifesto -> ferramentas MCP; o salto de resolução
admin.ts rotas de manifesto e de preset
presets/evo.ts EVO (academias): manifesto gerado + a tool_policy da plataforma
client.tsx browser tool tester
test/
tenantGateway.test.ts isolamento, manifesto, SSRF, projeção, execução
tenantResolve.test.ts salto de resolução, transform, raiz, query fixa
tenantAdmin.test.ts rotas admin de manifesto e de preset
evoPreset.test.ts o preset da EVO e o acordo com a tool_policyTestes: npm test (vitest).
Adding a network
Create src/social/<network>.ts exporting a register<Network>Tools(server, env) function, then add it to the SERVERS map in src/server.ts. For a Google product, reuse googleGet/googlePost from google-shared.ts so it shares the token refresh.
This 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
- Alicense-qualityCmaintenanceA minimal stateless MCP server for Cloudflare Workers that provides a greeting tool, demonstrating the use of createMcpHandler from the Agents SDK.270MIT
- Alicense-qualityCmaintenanceSimplest way to run a stateless MCP server on Cloudflare Workers, using createMcpHandler from the Agents SDK to handle MCP protocol details in one line.7MIT
- Flicense-qualityCmaintenanceDeploy a stateless remote MCP server on Cloudflare Workers without authentication, enabling connection from clients like Claude Desktop and Cloudflare AI Playground to use registered tools.
- Flicense-qualityCmaintenanceDeploy a stateless remote MCP server on Cloudflare Workers without authentication, with custom tool support and compatibility for MCP clients.
Related MCP Connectors
Cloudflare Workers MCP server: crypto-signal
Cloudflare Workers MCP server: email-validator
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
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/celfons/mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server