@wahlu/mcp-server
v0.11.3
Published
MCP server for the Wahlu social media management API
Maintainers
Readme
@wahlu/mcp-server
The Model Context Protocol adapter for Wahlu's agent-first
public API. Its hosted production inventory is exactly twenty-one
intention-level tools. The local stdio package adds a twenty-second local-only tool,
upload_media_from_file.
The isolated held inventory exposes exactly these eleven tools: get_context, list_targets,
get_platform_capabilities, import_media_from_url, get_media,
create_media_repair_derivative, create_draft, preflight_draft, create_schedule,
get_schedule, and get_publish_run_receipt.
| Category | Tools |
|----------|-------|
| Agent discovery | get_context, list_targets, get_target_dynamic_options, get_platform_capabilities |
| Media acquisition | import_media_from_url, list_media, upload_media, get_media, create_media_repair_derivative, upload_media_from_file (local stdio only) |
| Content | list_content_items, get_content_item, create_draft, update_draft_tiktok_privacy, preflight_draft |
| Schedules | list_schedules, create_schedule, get_schedule, get_publish_run_receipt, reschedule_schedule, cancel_schedule |
| Cleanup | cleanup_provider_publications (production only; exact receipt-bound cleanup) |
Every tool delegates to @wahlu/api-client. There is no generic HTTP transport, database or
storage access, second API retry implementation, generic polling loop, execution, approval,
or generic retry tool. Schedule mutations require exact confirmation and stable idempotency;
cancellation is limited to unsent Schedules with no execution history. The local file tool alone supports MCP cancellation and a bounded readiness
window. Production cleanup is limited to the exact public receipt
authority and supported provider publications.
For a selected TikTok target, call get_target_dynamic_options immediately before choosing
privacy, then pass one returned value with the same brand_id and integration_id to
update_draft_tiktok_privacy. Wahlu re-queries TikTok before changing only that existing draft and
rejects stale or unsupported values. Neither tool creates a replacement draft, Schedule, job,
queue entry, or provider post. Dynamic-option discovery requires integrations:write: its live
provider-backed refresh may rotate stored credentials, update provider-effect lease state, or mark
the integration for reauthorisation. Both tools are deliberately absent from the exact-eleven held
inventory.
API key
Create a Wahlu API key in the dashboard under Settings > API Keys, grant only the scopes the workflow needs, and restrict it to the intended brands where possible.
The executable reads these environment variables:
| Variable | Required | Default |
|----------|----------|---------|
| WAHLU_API_KEY | Yes | — |
| WAHLU_API_URL | No | https://api.wahlu.com |
| WAHLU_MEDIA_URL | No | https://media.wahlu.com |
Only override the two URLs for a Wahlu development or self-hosted environment. HTTPS is required except for an explicit loopback development origin.
Connect an agent
The npm package is a local stdio MCP server. The examples below use npx, so a compatible Node.js
runtime must be available to the host application.
Local agents can call upload_media_from_file with an absolute caller-owned file path. When the
file's context is known, provide a meaningful name with the real file extension and an optional
factual description. Omit the description when the context is insufficient; the tool does not
generate or guess one. Hosted HTTP MCP never accepts local paths.
The tool fingerprints the exact logical upload from its content digest and metadata, not its local path. For one bounded local-server session, a retry of that same logical upload reuses the first caller's idempotency key even if an agent invents a new key or the file has moved. The process-local registry expires entries after one hour and keeps at most 256; changing the content or upload metadata remains a distinct upload.
Slow PUTs honour MCP cancellation and emit throttled standard byte-progress notifications when the caller supplies a progress token. If the PUT connection ends ambiguously, the tool reads the known canonical Media once before deciding whether the upload failed, so accepted bytes are not reported as a new or missing Media.
After upload, the tool follows only that Media for up to 15 minutes, with at least 15 seconds between
reads and any longer API-recommended delay respected. It emits stage progress when supported, stops
immediately at ready, failed, stuck, or cancellation, and treats queued or processing as honest
successful results when the window ends. Use the returned progress.recommended_poll_after_ms
before checking again. The separate get_media tool always performs exactly one read and never
polls.
Uploading media that has no public URL
upload_media covers any image or video that is not already on a public URL, on hosted connectors
as well as locally, with no shared filesystem. Always supply brand_id, file_name, content_type,
the exact size and a stable idempotency_key. How the bytes travel depends on how big they are:
8 MB or less — send the bytes. Put them in content as {"type": "base64", "data": "…"} or an
MCP embedded resource. The declared type and size are verified against the real bytes, and the call
returns the finished media item.
Larger, including most video — stream them. Omit content and send content_sha256, the
lowercase hex SHA-256 of the file. The call returns a short-lived upload capability bound to that
exact media ID, content type, byte count and digest:
{ "id": "…", "status": "uploading",
"upload": { "method": "PUT", "url": "https://media.wahlu.com/uploads/…", "headers": { … },
"expires_at": "…" } }PUT the file to that URL with the returned headers, then call get_media with the same ID to
confirm it is ready. The URL is on Wahlu's own media origin — no storage-provider URL, object path
or provider credential is ever exposed. Wahlu re-sniffs the content type and re-checks the byte
count on arrival, so a payload that disagrees with what was declared is rejected. Normal media
policy applies: 25 MB for images, 500 MB for video.
Either way the returned ID can be used directly in a draft's media_ids or thumbnail_media_id.
Codex
Add Wahlu from a terminal, then restart the active Codex surface:
codex mcp add wahlu --env WAHLU_API_KEY=wahlu_live_replace_me -- npx -y @wahlu/mcp-serverConfirm it with codex mcp list or /mcp. Codex CLI, the IDE extension, and the Codex desktop app
share the same MCP configuration. See the Codex MCP guide.
Claude Code
Add Wahlu at user scope and confirm the connection:
claude mcp add --scope user wahlu --env WAHLU_API_KEY=wahlu_live_replace_me -- npx -y @wahlu/mcp-server
claude mcp get wahluUse /mcp inside Claude Code to inspect its tools. On native Windows, configure the command as
cmd /c npx -y @wahlu/mcp-server if npx cannot be launched directly. See Anthropic's
Claude Code MCP guide.
Claude Desktop and Cowork
For local Claude Desktop, add this server to claude_desktop_config.json through Developer settings
and restart Claude Desktop:
{
"mcpServers": {
"wahlu": {
"command": "npx",
"args": ["-y", "@wahlu/mcp-server"],
"env": {
"WAHLU_API_KEY": "wahlu_live_replace_me"
}
}
}
}Check the connection under Developer settings or Connectors in the composer. Local Cowork sessions can use locally configured MCP servers when that capability is enabled. Remote Cowork sessions cannot run this local npm process; they require a publicly reachable remote MCP connector. See Anthropic's guides for local MCP servers and remote MCP connectors.
ChatGPT web and hosted Work mode
This local stdio npm package does not connect to ChatGPT web or its hosted Work mode. Those
hosted surfaces use Wahlu's OAuth-backed ChatGPT App at https://mcp.wahlu.com/mcp. It uses the
existing Wahlu authorization service and the same workspace, brand, tenant, entitlement, and scope
checks as the hosted API. Do not paste a local npx command or an API key into ChatGPT web.
To test the private app in ChatGPT Developer Mode:
- In a web browser, open chatgpt.com. Do not use the ChatGPT desktop/Codex MCP settings for this flow.
- On the ChatGPT website, open Settings → Security and login and turn on Developer mode.
- Open ChatGPT Plugins, select the plus button, and enter
https://mcp.wahlu.com/mcpas the MCP server URL. - Complete the in-browser Wahlu sign-in and consent flow. Never enter a Wahlu API key in ChatGPT.
- Add the app to a new conversation and ask: “Show the latest media for my selected Wahlu brand.”
- Check that only that brand's media appears, previews load from
media.wahlu.com, and an empty brand shows a useful empty state. - After any tool, schema, OAuth, or widget-resource change, open the app detail page and select Refresh before retesting.
An existing local Codex MCP entry such as wahlu_-private- is separate from this web connection.
Leave it unchanged; the private ChatGPT review does not require deleting, disabling, uninstalling,
or recreating it.
The production hosted surface includes live
target-bound TikTok privacy discovery, exact existing-draft privacy updates, explicit reviewed
repair derivatives, and exact
receipt lookup and receipt-bound provider cleanup. The isolated candidate/live-test surface
supports eleven held tools and cannot advertise or use cleanup or publish:execute. It creates
Schedules through the normal Wahlu permission checks: pending_review stays held, while an approved
Schedule additionally requires publish:execute and may publish later through the established
pipeline. The repository's chatgpt-app-submission.json is a submission-form draft; no app has
been submitted or published from this repository.
The hosted app adds six compact result widgets: integration health for list_targets, the media
library for list_media, media detail for get_media, post review for create_draft, the calendar
for list_schedules, and post status shared by get_schedule and
get_publish_run_receipt. They render scoped tool results; the media widgets use opaque Wahlu media
delivery URLs supplied only through widget _meta, redacted from model-visible structuredContent
and text content, without fetching a second API or exposing storage paths. Each media widget keeps
the exact authenticated Wahlu app link primary and the raw media download secondary. Deterministic
descriptor, resource, CSP, schema, and safety checks live in the apps/mcp and
packages/mcp-server suites, so most changes can be verified without a live OAuth client or
production deployment. See OpenAI's
MCP server guide,
authentication guide, and
Developer Mode testing guide.
Library-only held HTTP adapter
The package also exports createHeldMcpHttpHandler for local, injected integration tests and future
authenticated resource-server wiring. It accepts only loopback peers and a loopback Host, serves
only /mcp, and creates fresh authority, client, MCP server, and transport state for every POST.
It never reads WAHLU_API_KEY or another process-wide identity.
Callers must inject an immutable request authority, a fresh client for the held eleven-tool inventory,
the canonical remote media URL validator, and a bounded application audit sink. There is no
anonymous/default authority
or header-based test-account bypass. The held adapter keeps get_publish_run_receipt read-only by
stripping cleanup authority and cleanup links, omits the cleanup tool, and constrains
create_schedule to pending_review; it cannot represent approved or publish:execute.
This adapter is deliberately stateless. It does not issue MCP session IDs and returns 405 for GET
or DELETE. SSE, resumability, event storage, tasks, prompts, resources, sampling, elicitation,
server notifications, OAuth parsing, legacy HTTP+SSE, and any approval, execution, queue, provider,
or publishing tool are absent. OAuth, public hosting, and durable deployment wiring are separate
work and are not configured by this package.
Held Schedule workflow
- Call
get_contextto inspect the workspace, accessible brands, and exact API-key scopes. - Call
list_targetsfor the selected brand and repair any reported blockers. - Call
get_platform_capabilitiesbefore constructing platform-specific content. - Call
import_media_from_urlwith a public HTTP(S) URL and stableidempotency_key. - Call
get_mediaonce when its current readiness is needed. The tool does not poll. - Call
create_draftwith canonical content and a stableidempotency_key. - Call
preflight_draftwith the intended targets, time, and explicit approval state. - Call
create_schedulewith an explicitapproval_statusand stableidempotency_key. - Call
get_scheduleonce to inspect its canonical status, blocker, and next action. - If a known Schedule has a publish run, call
get_publish_run_receiptto inspect its redacted per-platform outcomes. The held projection does not include cleanup authority or cleanup links.
A pending_review Schedule is held with APPROVAL_PENDING. It creates no execution or job and has
no publishing-provider effect. An explicit approved Schedule additionally requires
publish:execute and may later cause an external publication. Tool descriptions, static worst-case
metadata, and result-specific safety fields preserve that distinction.
Structured success preserves the API's canonical meta object. HTTP status and idempotency replay
truth are separate under transport. A replay returns the existing resource and is not reported as
a new write. Errors remain structured failures rather than inferred success.
Example prompts
Use get_context, list_targets, and get_platform_capabilities to show what this key can safely do.
Import this public campaign image with a stable key, then read the media item once.
Create a draft, preflight it, and create a pending_review Schedule with stable idempotency keys.
Then read the Schedule once and report its blocker and next action. Do not approve or publish it.Documentation
License
MIT
