MCP server for Layers — connect Claude, Cursor, Windsurf to your workspace (73 tools)
10K+
A Model Context Protocol server for Layers. Connect assistants to your workspace to read and manage tasks, pages, projects and forms with your account's permissions.
Protocol: MCP Streamable HTTP (2025-06-18 spec) | Tools: 73 | Transport: HTTP
The endpoint is https://YOUR_DOMAIN/mcp. Use the domain of your own installation.
The connection guide covers both OAuth
and manually generated tokens. A self-hosted instance serves its own guide at
/mcp/docs/start/connect/ and copyable settings at /mcp/setup.
claude mcp add --transport http layers https://YOUR_DOMAIN/mcp
Open /mcp inside Claude Code to authenticate. claude mcp list checks connection
status. Tool discovery on the default full catalogue should show layers with 73 tools;
the number of available tools depends on the selected catalogue.
.cursor/mcp.json:{
"mcpServers": {
"layers": {
"url": "https://YOUR_DOMAIN/mcp"
}
}
}
.vscode/mcp.json:{
"servers": {
"layers": {
"type": "http",
"url": "https://YOUR_DOMAIN/mcp"
}
}
}
Start the connection and follow the client's authentication prompt. Other clients may have different configuration formats; remote HTTP support does not itself guarantee OAuth support.
Open Account Settings > Access Tokens, create a named token with an expiry, and copy it once. Start with read-only access for inspection; enable writes only when needed. The token cannot grant rights your account does not have.
Send Authorization: Bearer YOUR_TOKEN to the endpoint. For a client that accepts
headers in this configuration:
{
"mcpServers": {
"layers": {
"url": "https://YOUR_DOMAIN/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
Keep actual tokens out of Git, URLs, screenshots and messages. Use protected client settings or environment-backed secrets. Revoke a compromised token in account settings.
For Claude Desktop's local configuration or another stdio-only client, install
a supported Node.js LTS version compatible with mcp-remote and use:
{
"mcpServers": {
"layers": {
"command": "npx",
"args": ["-y", "mcp-remote@latest", "https://YOUR_DOMAIN/mcp"]
}
}
}
The bridge can perform OAuth. For token authentication, add
"--header", "Authorization:Bearer YOUR_TOKEN" to args. Do not commit a real token.
Claude Desktop reads local configuration from
~/Library/Application Support/Claude/claude_desktop_config.json on macOS or
%APPDATA%\\Claude\\claude_desktop_config.json on Windows. Fully quit and reopen the
app after editing; if npx is not found, use its full path.
Ask which workspace and projects are accessible before testing writes. A successful
GET of /mcp shows the server version, not proof that authenticated tool calls work.
Use ?profile=pages, tasks, forms or another supported profile for a smaller
catalogue. A readonly catalogue hides writes; it does not change the token's rights.
If a write times out, read the object before retrying: the change may already have happened. Review the troubleshooting guide and data handling before deployment.
Add the MCP server to your Layers Docker Compose stack.
If you are standing up the full self-hosted Layers bundle — the Layers server, this MCP server, and the Layers AI Chat — this service is the middle layer. The request flow is:
browser → AI Chat → (internal network) MCP server :8091 → Layers REST API → Postgres
The chat (and any other in-cluster consumer) talks to the MCP server over the internal Docker network, sending each user's workspace token as a bearer:
LAYERS_MCP_URL = http://layers-mcp:8091/mcp
Internal endpoint vs. public endpoint.
http://layers-mcp:8091/mcpis the internal address, reachable only from inside the Docker network and authorized by the forwardedAuthorization: Bearer layers_ws_…. If you also expose/mcppublicly through your reverse proxy (for external AI assistants), that public URL normally sits behind your own SSO / proxy authorization and is a separate entry point. In-stack consumers like the chat must target the internallayers-mcp:8091address directly — not the public URL.
Issuing the workspace token the chat uses. The chat sends a Layers workspace
token as the bearer. Create it once in your Layers instance under
Settings > Access Tokens (it starts with layers_ws_… and is shown only once),
then hand it to the chat as described in its
hosting guide.
In the self-host bundle the image is pulled as
hissih/layers-mcp-server; the
env vars below (LAYERS_BACKEND_BASEURL, LAYERS_BACKEND_FORWARDINCOMINGAUTH, …) are
the same regardless of which tag you pull. Point LAYERS_BACKEND_BASEURL at the
Layers API over the internal network (e.g. http://layers-server:8080). The full
end-to-end stack assembly (domains, SSO cookie, reverse proxy, voice) is documented
in the chat repo — see its
README and
docs/hosting-guide.md.
Add this service alongside your existing layers-server and layers-db:
layers-mcp:
image: sinups/layers-mcp-server:latest
restart: always
depends_on:
- layers-server
environment:
LAYERS_BACKEND_BASEURL: http://layers-server:8080
LAYERS_BACKEND_FORWARDINCOMINGAUTH: "true"
LAYERS_BACKEND_REQUIREINCOMINGAUTH: "true"
LAYERS_BACKEND_BEARERTOKEN: ""
# Address of the web app as a person opens it — links in tool output come from here
LAYERS_BACKEND_FRONTENDBASEURL: https://app.example.com
LAYERS_MCP_ENDPOINT: /mcp
SERVER_PORT: "8091"
SPRING_PROFILES_ACTIVE: server
ports:
- "8091:8091"
networks:
- layers-internal
Users connect to https://YOUR_DOMAIN/mcp using their personal API tokens from Settings > Access Tokens.
If your Layers instance uses a reverse proxy (nginx, Traefik, Caddy), route /mcp to the MCP container on port 8091. Example for Traefik labels:
labels:
- traefik.enable=true
- "traefik.http.routers.layers-mcp.rule=Host(`YOUR_DOMAIN`) && PathPrefix(`/mcp`)"
- traefik.http.routers.layers-mcp.priority=200
- traefik.http.services.layers-mcp.loadbalancer.server.port=8091
With nginx:
location /mcp {
proxy_pass http://layers-mcp:8091;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_buffering off;
}
If you want to run the MCP server separately (not in the Layers Docker network):
docker run -d \
--name layers-mcp \
-p 8091:8091 \
-e LAYERS_BACKEND_BASEURL=https://YOUR_DOMAIN \
-e LAYERS_BACKEND_FORWARDINCOMINGAUTH=true \
-e LAYERS_BACKEND_REQUIREINCOMINGAUTH=true \
sinups/layers-mcp-server:latest
For personal use or testing, you can set a static token so clients don't need to provide their own:
docker run -d \
--name layers-mcp \
-p 8091:8091 \
-e LAYERS_BACKEND_BASEURL=https://YOUR_DOMAIN \
-e LAYERS_BACKEND_FORWARDINCOMINGAUTH=false \
-e LAYERS_BACKEND_REQUIREINCOMINGAUTH=false \
-e LAYERS_BACKEND_BEARERTOKEN="Bearer layers_ws_YOUR_TOKEN" \
sinups/layers-mcp-server:latest
Then connect clients to http://localhost:8091/mcp with no Authorization header needed.
| Environment Variable | Default | Description |
|---|---|---|
LAYERS_BACKEND_BASEURL | https://api.layers.md | URL of the Layers API |
LAYERS_BACKEND_BEARERTOKEN | (empty) | Static token for single-user mode |
LAYERS_BACKEND_FORWARDINCOMINGAUTH | true | Forward client's Authorization header to Layers |
LAYERS_BACKEND_REQUIREINCOMINGAUTH | true | Reject requests missing Authorization header |
LAYERS_MCP_ENDPOINT | /mcp | MCP endpoint path |
LAYERS_MCP_LOGTOOLCALLS | true | Log tool calls with arguments and duration |
SERVER_PORT | 8091 | HTTP port |
73 tools grouped by what they work on. Every tool carries MCP annotations (readOnlyHint, destructiveHint, idempotentHint), so a client can ask before anything destructive happens.
This list is generated from the server's own catalogue — see scripts/render-readme-tools.mjs.
| Tool | Description |
|---|---|
layers_dashboard_activity | Get activity feed for a workspace or project |
layers_workspace_context | Get workspace overview: projects, members, recent pages (call first!) |
layers_workspace_items_hierarchy_tree | Get full workspace hierarchy (projects, folders) |
layers_workspaces_list | List all workspaces the token has access to |
| Tool | Description |
|---|---|
layers_people | who is in the workspace, in a project, who can open a page; find one person by name or email |
| Tool | Description |
|---|---|
layers_project_create | Create a project in a workspace |
layers_project_dashboard | Get project dashboard stats (task counts by status) |
layers_project_get | Get a project by ID |
layers_project_update | Rename, recolour or archive a project |
layers_projects_tree_by_workspace | List all projects in a workspace |
| Tool | Description |
|---|---|
layers_sprint_create | Create a sprint in a project |
layers_sprint_get | Get a sprint by ID |
layers_sprint_list | List sprints of a project |
layers_sprint_move | Move a sprint to another project |
layers_sprint_update | Rename a sprint, change its dates, or close it |
| Tool | Description |
|---|---|
layers_milestone_create | Create a milestone |
layers_milestone_get | Get a milestone by name or ID |
layers_milestone_update | Update a milestone — name, date, description |
| Tool | Description |
|---|---|
layers_comment_resolve | Mark a comment resolved — the tick people put on a thread once it is dealt with |
layers_task_assign | Assign or unassign a task |
layers_task_attachment_upload | Attach a file to a task — any size via a one-time upload link, so the bytes never pass through the conversation |
layers_task_comment_create | Leave a comment on a task — markdown is accepted and arrives formatted |
layers_task_comment_update | Edit a comment on a task |
layers_task_comments_list | The discussion on a task: comments with their reply threads |
layers_task_create | Create a new task (auto-resolves projectName, assigneeName, statusName) |
layers_task_get | Get a task by ID |
layers_task_list_smart | Smart task list with natural-language filters (overdue, unassigned, mine) |
layers_task_update | Update task title, description, assignee, or status |
| Tool | Description |
|---|---|
layers_comments | read the comments of a task or a page, or of many tasks at once |
| Tool | Description |
|---|---|
layers_tag_create_or_get | Create a tag, or return the existing one with that name (safe to retry) |
| Tool | Description |
|---|---|
layers_favorite_add | Add a page, project or folder to the person's favourites |
layers_favorite_remove | Remove a page, project or folder from the person's favourites |
layers_page_append | Append content to the end of a page without rewriting it |
layers_page_comment_create | Leave a comment on a page — markdown is accepted and arrives formatted |
layers_page_comments_list | The discussion on a page: comments with their reply threads |
layers_page_create | Create a new page with rich HTML content |
layers_page_duplicate | Duplicate a page (with sub-pages) |
layers_page_get | Get a page by ID |
layers_page_list_by_workspace | List pages across the workspace |
layers_page_move | Move a page to a different folder or project |
layers_page_settings | Read a page's settings — lock, width, pin, visibility |
layers_page_share | Share a page with a person by email |
layers_page_update | Update a page; pass the version you read to avoid overwriting someone else's edit |
layers_page_visibility | Change who can see a page |
| Tool | Description |
|---|---|
layers_folder_contents | List contents of a folder (pages, subfolders) |
layers_folder_create | Create a folder inside a project or another folder |
layers_folder_get | Get a folder by ID |
layers_folder_update | Rename or move a folder |
| Tool | Description |
|---|---|
layers_attachment_download | private one-time link to download an attachment of a task, page or Disk file |
| Tool | Description |
|---|---|
layers_disk | list files and folders on the Disk — My files, Shared with me, or a folder by path |
| Tool | Description |
|---|---|
layers_form_brief | Turn a plain-language brief into a form draft |
layers_form_create | Create a new form |
layers_form_get | Get a form by ID |
layers_form_list | List forms, including forms inside projects |
layers_form_media_search | Find images usable as form backgrounds |
layers_form_publish | Publish a form and get its public link |
layers_form_update | Update form fields or settings |
| Tool | Description |
|---|---|
layers_favorites | what is in the person's favourites |
layers_form_results | a form's answers, or its counts and completion rate |
layers_reference | reference for composing: page blocks, form themes, form templates, form CSS classes |
| Tool | Description |
|---|---|
layers_search | Full-text search across tasks, pages, and projects |
| Tool | Description |
|---|---|
layers_navigate | Walk the hierarchy one level at a time: what is inside this thing? |
| Tool | Description |
|---|---|
layers_user_me | Get the currently authenticated user |
| Tool | Description |
|---|---|
layers_invitation_accept | Accept a workspace invitation |
layers_invitation_delete | Revoke a pending invitation |
layers_workspace_invite | Invite a person to a workspace by email, with a role |
layers_workspace_kick | Remove a member from a workspace |
layers_workspace_usage_summary | Seats, storage and plan limits for a workspace |
| Tool | Description |
|---|---|
layers_delete | delete a task, page, project, list, folder, form, milestone, tag, comment or attachment — to the trash |
layers_restore | bring any of them back from the trash |
| Tool | Description |
|---|---|
layers_task_bulk_create | Create many tasks in one call |
layers_task_bulk_update | Update many tasks in one call |
| Tool | Description |
|---|---|
layers_toolsets | List the tool slices this server can expose, and their token cost |
Workspace Context — Call layers_workspace_context first to get a quick overview of projects, members, and recent pages. The AI assistant will know exactly where to create content.
Name Resolution — Use human-readable names instead of UUIDs:
"Create a task in project Design v2 and assign to Alice"
The server resolves projectName, assigneeName, statusName, and priorityName automatically.
Disambiguation — When a name matches multiple items, the server returns a helpful list:
Multiple projects match 'MCP'. Please be more specific:
- MCP Testing (id=b0f02a3d...)
- MCP Integration Test (id=dc399ea8...)
Use the exact name or provide the ID directly.
Auto-defaults — Missing fields get smart defaults:
Browse your workspace
"What workspaces do I have access to?"
Find overdue work
"Show me all tasks that are overdue"
Create a task
"Create a task called 'Fix login bug' in project Alpha and assign it to me"
Read a document
"Read the PRD page"
Check project health
"What's the progress on the Mobile App project?"
Plan a sprint
"List all unassigned tasks in the Backend project"
Manage team
"Who are the members of the Design project?"
cat your-config.json | python3 -m json.toolnode --version (need 18+)npx (see Claude Desktop section above)Symptom: Server connects, lists 73 tools, but tool calls return 401.
Cause: Your token is from a different Layers instance than the MCP server URL.
Fix: Make sure the MCP server URL and the token are from the same Layers instance. A token created on workspace-a.layers.md will not work with an MCP server pointing to workspace-b.layers.md.
Symptom: 401 Unauthorized on all requests.
Fix: Create a new token in Settings > Access Tokens. Tokens start with layers_ws_ and are shown only once.
Symptom: 403 with TOKEN_SCOPE_READ_ONLY on any create / update / assign /
comment / delete, while listing and search keep working.
Fix: The token was issued for reading only. Access level is chosen once, at creation, and cannot be changed afterwards — issue a new token in Settings > Access Tokens with Access: read and write and swap it into your client config. Retrying, or reaching for a different tool, will not help.
Symptom: Browser opens asking for OAuth login instead of using the token.
Fix: Clear the mcp-remote auth cache and restart:
rm -rf ~/.mcp-auth
Fix: Set a longer timeout with the MCP_TIMEOUT environment variable:
{
"mcpServers": {
"layers": {
"command": "npx",
"args": ["mcp-remote", "https://YOUR_DOMAIN/mcp", "--header", "Authorization:Bearer YOUR_TOKEN"],
"env": {
"MCP_TIMEOUT": "10000"
}
}
}
}
Add your CA certificate:
"env": {
"NODE_EXTRA_CA_CERTS": "/path/to/corporate-ca.crt"
}
npx mcp-remote https://YOUR_DOMAIN/mcp --debug
Logs are written to ~/.mcp-auth/.
AI Assistant (Claude / Cursor / Windsurf / VS Code)
|
| MCP Streamable HTTP (2025-06-18)
| Authorization: Bearer layers_ws_...
v
+------------------------------+
| Layers MCP Server | :8091/mcp
| Java 17 + Spring Boot 4 |
| 73 tools, 10 resources |
| 8 prompts, 8 completions |
+-------------+----------------+
| REST API + Bearer token (forwarded)
v
+------------------------------+
| Layers API | your-domain.com
+------------------------------+
Multi-user mode (default): Client token -> MCP Server -> Layers API (forwarded as-is)
Single-user mode: MCP Server static token -> Layers API (client sends no token)
./mvnw -DskipTests package
java -jar target/layers-mcp-server.jar \
--spring.config.location=file:./application-external.properties \
--spring.profiles.active=server
See deploy/application-external.properties.example for configuration template.
docker pull sinups/layers-mcp-server:latest
Available for linux/amd64 and linux/arm64.
Content type
Image
Digest
sha256:12e65ea37…
Size
143.1 MB
Last updated
about 3 hours ago
docker pull sinups/layers-mcp-server