@layers/mcp-server
v1.3.2
Published
MCP server exposing the Layers API as tools
Readme
Layers MCP Server
An MCP (Model Context Protocol) server that exposes the Layers API as tools, so AI agents can manage projects, generate short-form social content, and publish it — straight from any MCP client.
Tool coverage tracks the Layers API reference.
One-paste onboarding
Paste this one sentence to your coding agent, from the product repository:
Onboard this repository into Layers: run npx --yes @layers/[email protected] onboard from the repo root as a background process and follow the agent_instructions it prints; each input_required event advertises the exact command to send, except approve_consent, where you print the consent_proposal verbatim and stop until the human explicitly approves.That is the whole paste. The consent clause is not decoration: approve_consent
advertises its approval command like every other turn, so a paste that says only
"send the advertised command" reads as licence to send that one too. The
exception has to be stated where the human pastes it, not only inside the
launcher output the agent may summarize.
The rest of the rules for driving the launcher used to live in the paste; they now arrive from the launcher itself, as the FIRST line it writes:
{"type":"agent_instructions","protocolVersion":1,"instructions":"…","commands":{…}}instructions is the full operating protocol in plain ASCII — how to poll the
process, what to print verbatim, when to stop and wait for a human, and what to
do with the SDK fact after the claim. commands lists the command shapes the
protocol uses; every turn still advertises its own exact command, and that
advertised string is the one to send. The event is emitted once, before the
launcher does anything else, so an agent that started the process has the
protocol before it has to drive anything.
The phrase "as a background process" is load-bearing: Claude Code's permission
classifier approves a long-running npx only when it is told the task runs in
the background. Keep it.
The paste pins an exact version, on purpose. The server's capability
manifest is the source of truth for which launcher it will accept: it advertises
minimumMcpServerVersion and acceptedCollectorVersions, and a launcher outside
that window is refused at preflight before anything is reserved. Pinning the
paste to the version the server currently advertises means the paste and the
server agree by construction. @latest does not: it can outrun a cutover and
hand somebody a launcher the server has not been told about yet, which fails with
"requires an update" for a version that is newer than the one being demanded. So
the paste stays pinned and moves when the server's advertised version moves.
The same pin applies to the Claude Code recipe below, which is the paste's own
command with a FIFO wired to its stdin — a recipe that drifted to @latest
would quietly contradict the sentence it implements. The legacy public-URL form
further down stays on @latest: it predates the capability manifest and is not
part of the pinned flow.
The command checks server compatibility before reading the workspace, runs the checksum-verified native collector locally, shows the exact bounded source-data proposal, and waits for explicit approval before sending evidence. While the preview builds it asks the same setup questions the Layers web onboarding asks, one at a time, each carrying its exact title, its offered options, and the exact command that answers it. It then surfaces the preview and attempt-bound browser claim URL and returns the safe post-claim workspace projection to the same process when the browser claim finishes within its bounded window. Reservation, transport, PKCE verifier, post-claim capability, full evidence-envelope, and transient excerpt values never enter terminal output; the bounded consent projection does.
Setup questions and the claim link
Once evidence is approved and on its way, the command reads the outstanding
setup questions and emits input_required: answer_intake one question at a
time. Each turn carries the canonical question (title, optional subtitle,
whether it takes one option or several, whether it takes free text, and the
offered options as value/label pairs) and the commands that answer it:
answer <field> <value> pick one offered option
answer <field> <value>,<value> pick several (multi-select questions)
answer <field> pick none (multi-select questions)
answer <field> other <your own words> questions whose allowsFreeText is trueWhich questions take free text is the server's call, carried per question on the
wire and reported on the turn as allowsFreeText. A question the walk refuses an
answer to is re-asked with the server's reason on refusal, rather than reported
as recorded.
Send exactly one advertised command per turn; the next question arrives once the answer is recorded. A line that names an option the question does not offer re-asks the same question rather than guessing.
The claim link waits for both the preview and the questions. Whichever
finishes second releases it, so the browser claim is never offered while setup
questions are still outstanding. Progress is reported on intake events, whose
complete flag is the explicit signal that the walk is done — the same way
previewReady reports the preview. state is one of asking, complete,
not_required (there were no questions), or skipped. skipped means the
gate failed open: the question service was unreachable, refused repeatedly, or
the questions were left unanswered past their bounded window. A broken question
service costs a person their questions, never their workspace. The terminal
complete event carries the same summary on its intake field.
Waiting for the browser claim
The claim handoff waits for the human, not for a timer. A browser claim link is
attempt-bound and the server caps each attempt at 15 minutes, so an unopened link
is re-minted rather than treated as the end of the session: the command emits
status with stage claim_link_refreshed, and the fresh URL arrives on the next
progress event. Give the human that one; the earlier link is dead.
While it waits it emits status with stage awaiting_claim every few minutes,
so an agent polling a deliberately idle process has something true to report. The
overall wait is 24 hours, intersected with the reservation.
If it does stop before the claim, it says so explicitly on status with stage
claim_still_open before the terminal complete: the workspace is still built
and still claimable until the reservation expires, and nothing was cancelled.
1.3.0 ended the wait at the 15-minute transport-attempt expiry and exited
0withstate: awaiting_claim, which read as "the claim expired" when the only thing that had expired was the transport leg. That is fixed in 1.3.1.
Claude Code process control
The command stays alive across scope review, human approval, preview creation,
and claim. Each local source-inspection generation has a fixed 15-minute
privacy lifetime. If that lifetime ends during scope review or consent, the
command clears the expired local artifact and emits
input_required: resume_inspection; resume performs a fresh inspection with
fresh opaque IDs and requires a fresh proposal and approval. An expired
proposal can never authorize an upload. In Claude Code, run the command as a
native background Bash task so later tool calls can read its JSONL and answer
its stdin. Do not put &, nohup, or a foreground wait around the command;
those do not give Claude a controllable task handle.
In a short foreground Bash call, create the private input pipe and retain the absolute directory printed by the last line:
layers_session_dir="$(mktemp -d "${TMPDIR:-/tmp}/layers-onboard.XXXXXX")" chmod 700 "$layers_session_dir" mkfifo "$layers_session_dir/input" chmod 600 "$layers_session_dir/input" printf '%s\n' "$layers_session_dir"In a second Bash tool call, substitute that absolute path and run exactly this shell with the Bash tool's
run_in_backgroundparameter set totrue:exec 3<>"/absolute/layers-onboard.ABC123/input" npx --yes @layers/[email protected] onboard <&3The shell command itself stays in the foreground of the background task. Claude Code returns a task ID instead of blocking the conversation.
Use
TaskOutputon that task ID withblock: trueand a timeout no longer than 15 seconds to read new JSONL. Send each advertised response from a separate short Bash call, for example:printf '%s\n' 'prepare' > '/absolute/layers-onboard.ABC123/input'Read
TaskOutputagain after every response. Print the completeconsent_proposal.canonicalProjectionverbatim, along with its display ID, display time, projection hash, and exact advertised approval command. End the turn and wait for the human's explicit approval before writing that command. Ifresume_inspectionappears, send only its advertisedresumeorcancelcommand; a resumed inspection requires a new proposal and new approval. Keep the task alive until a terminalcompleteorerrorevent.At
input_required: answer_intake, relay one question per turn while the launcher keeps building in the background: show its exacttitle, itssubtitlewhen present, and its offeredoptions, take an offered option or the human's own words, and send the exact advertised answer command before asking the next. Never show a setup question while a consent proposal is displayed or awaiting approval. Withhold the claim link until the launcher reports the questions complete and the preview ready.
For ordinary public callers, a server that has not opened source admission stops the command before local inspection. An operator-only environment token can authorize a closed internal probe. The older public-URL compatibility form remains available:
npx --yes @layers/mcp-server@latest onboard https://example.comInstall as an MCP server
To expose the persistent Layers MCP tools without an existing API key:
claude mcp add layers -- npx -y @layers/mcp-server@latestThe server enters keyless onboarding mode when neither --api-key nor
LAYERS_API_KEY is set. It can create and claim a workspace without an
existing Layers account or API key.
To connect an existing Layers workspace with an API key:
claude mcp add layers -- npx -y @layers/mcp-server@latest --api-key lp_YOUR_KEYOr configure API-key mode in any MCP client's JSON config:
{
"mcpServers": {
"layers": {
"command": "npx",
"args": ["-y", "@layers/mcp-server@latest", "--api-key", "lp_YOUR_KEY"]
}
}
}Pin a reviewed version (
@layers/mcp-server@<version>) in production setups instead of@latestif you want releases to be deliberate on your side.
Configuration
Flags take precedence; environment variables are the fallback.
| Flag | Env var | Default | Description |
|---|---|---|---|
| --api-key | LAYERS_API_KEY | unset | Layers API key (lp_...). When neither form is set, the server starts in keyless onboarding mode. |
| --base-url | LAYERS_BASE_URL | https://api.layers.com | API host. Paths are versioned under /v1. |
| --read-only | LAYERS_READ_ONLY=1 | off | In API-key mode, register only the 25 read tools. In keyless mode, this limits the workspace API tools; the five onboarding tools remain available. |
| --organization | LAYERS_ORGANIZATION | unset | Act on behalf of a child org (org_...), sent as the X-Layers-Organization header on every request. Requires an org:admin parent key. |
Security note: flags end up in your client's config file and in the process
argv (visible via ps). Prefer the env var for the key where your client
supports it:
{
"mcpServers": {
"layers": {
"command": "npx",
"args": ["-y", "@layers/mcp-server@latest"],
"env": { "LAYERS_API_KEY": "lp_YOUR_KEY" }
}
}
}Sandbox keys (lp_test_...) skip real platform calls during development —
content, OAuth, and publish return fixture-backed results.
Tools
The tool surface depends on how the server starts:
- API-key mode: 52 workspace tools, one per API route.
- Keyless onboarding mode: five onboarding tools plus the same 52 workspace tools. The workspace tools are registered up front so the MCP client can see them, but they refuse calls until the onboarding session claims a workspace.
--read-only hides workspace write tools (marked W below). The five onboarding
tools remain available in keyless mode.
Keyless onboarding tools
onboard_start · get_onboarding_status · onboard_claim_begin ·
onboard_claim_verify · ask_elle
onboard_start without a URL remains a reservation-only MCP tool. Its result is
honestly limited to awaiting_evidence, and its opaque capability stays inside
the server process. The full local inspection, consent, evidence, preview,
browser claim, and same-process return path belongs to the one-shot
layers-mcp-server onboard command above. Passing an explicit public product
URL to the MCP tool keeps the existing preview and claim flow working.
Workspace API tools (52)
Core
whoami · list_projects · get_project · get_credits ·
list_credit_events · create_project (W) · update_project (W) ·
archive_project (W)
Creative
list_influencers · get_influencer · get_keywords · list_content ·
get_content · get_content_progress · get_content_asset · get_hooks ·
get_source_recommendations · get_content_review_policy ·
create_influencer (W) · clone_influencer (W) · update_influencer (W) ·
delete_influencer (W) · refresh_keywords (W) · generate_slideshow (W) ·
generate_ugc_remix (W) · generate_video_remix (W) ·
generate_slideshow_remix (W) · create_content_upload (W) ·
upload_content_from_url (W) · finalize_content_upload (W) ·
update_content_caption (W) · approve_content (W) · reject_content (W) ·
update_content_review_policy (W)
Distribution
list_social_accounts · get_scheduled_post · list_scheduled_posts ·
list_tiktok_music · get_engagement_config · publish_content (W) ·
schedule_content (W) · reschedule_post (W) · cancel_scheduled_post (W) ·
notify_device (W) · update_engagement_config (W)
Measurement
get_metrics · get_top_performers · list_ads_content ·
list_recommendations · update_ads_content (W) · update_recommendation (W)
Framework
list_audit_log
Conventions
- Async jobs. Generation, influencer creation/cloning, and keyword refresh
return a
202job envelope (jobId,containerIds/influencerId). Poll the matching read tool (get_content_progress,get_influencer,get_keywords) until the resource is terminal. - Uploading your own media. Two transports, both producing an uploaded
content container you can then schedule/publish. For already-hosted files,
upload_content_from_urlis one synchronous call. For large/private files,create_content_uploadreturns presignedPUTURLs — your client uploads the bytes directly to storage (outside this server, within ~15 min), then callsfinalize_content_uploadper container. Fix a caption afterward withupdate_content_caption(uploaded content only). - Pagination. List tools accept
cursor+limitand return{ items, nextCursor }; passnextCursorback verbatim. - Idempotency. The server stamps a fresh UUID
Idempotency-Keyon every mutating POST/PATCH automatically. - Errors expose only the public status, stable error code, safe message, and
validated
requestId. Backend details and non-contract response bodies are not copied into the agent transcript. Include therequestIdin support tickets. - Timestamps are ISO 8601 UTC with a
Zsuffix everywhere (offset forms are rejected by the API).scheduledForis a literal UTC instant — convert from local time before calling.
Development
Requires Node 20+.
npm install
npm run build # tsc -> dist/
# wire the local build into Claude Code:
claude mcp add layers -- node $(pwd)/dist/index.js --api-key lp_YOUR_KEY
# exercise the same-session command from a product workspace:
(cd /path/to/product && node /path/to/mcp-server/dist/index.js onboard)
# or explore interactively with the MCP inspector:
npx @modelcontextprotocol/inspector node dist/index.js --api-key lp_test_dummyIn MCP-server mode, stdout is the JSON-RPC channel and all logging goes to stderr. The one-shot onboarding CLI emits its documented JSONL events on stdout; the legacy URL form emits its compatibility output there.
Testing
npm test # hermetic suite — no API key, no network
npm run smoke # opt-in live smoke; needs LAYERS_TEST_KEY=lp_test_...npm test builds, then runs the contract suite with Node's built-in test runner
against a localhost mock — it verifies tool registration, --read-only gating,
annotation hints, stdout protocol discipline, and the request contract (auth,
idempotency, query encoding, per-tool routing, error rendering). No credentials or outbound network
required; this is what CI runs. See test/README.md for the
full breakdown and the sandbox smoke script.
License
Apache-2.0 — see LICENSE and NOTICE. Copyright 2026 Layers AI, Inc.
Report vulnerabilities privately as described in SECURITY.md.
