Civitai MCP Server
Enables AI agents to fully interact with the Civitai platform, including browsing models, images, and creators; posting, reacting, reviewing, following, collecting, writing articles and comments; sending DMs; and managing bounties and announcements.
README
Civitai MCP Server
Turn your AI agent into a full Civitai participant.
A Model Context Protocol server that gives AI agents first-class access to Civitai: browse models, images, and creators; post and publish work; react, review, follow, and collect; write articles and comments; send and reply to direct messages; create and enter bounties; and (for moderators) manage site announcements and the changelog. 53 tools, exposed over Streamable HTTP or stdio.
There are two ways to use it:
- Hosted (recommended) — point your MCP client at
https://mcp.civitai.com/mcp. Zero install. - Self-host — run it yourself with pnpm, Docker, or in Kubernetes.
Quick start — hosted (recommended)
The easiest path: add the hosted endpoint to your MCP client and supply your own Civitai API key. Nothing to install or run.
- MCP endpoint:
https://mcp.civitai.com/mcp - Transport: Streamable HTTP
- Auth: send
Authorization: Bearer <YOUR_CIVITAI_API_KEY>with each request
Browse / read tools (search models, images, creators, etc.) work without a key. Everything that writes (posting, reacting, commenting, DMs, bounties, ...) needs one. Grab a key at civitai.com/user/account.
Claude Code (CLI)
claude mcp add --transport http civitai https://mcp.civitai.com/mcp \
--header "Authorization: Bearer YOUR_CIVITAI_API_KEY"
Claude Desktop / claude.ai (custom connector)
Note: the claude.ai / Claude Desktop custom-connector UI is OAuth-only — it has no field for a static API key or custom header (anthropics/claude-ai-mcp#112). Because this server currently runs in token-only mode (
OAUTH_ENABLED=false), the custom-connector flow won't work yet. Use Claude Code, Cursor, or any client that supports a bearer header. One-click OAuth connect will work here once Civitai OAuth ships andOAUTH_ENABLED=true.
Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"civitai": {
"url": "https://mcp.civitai.com/mcp",
"headers": { "Authorization": "Bearer YOUR_CIVITAI_API_KEY" }
}
}
}
Generic .mcp.json (any client)
{
"mcpServers": {
"civitai": {
"type": "http",
"url": "https://mcp.civitai.com/mcp",
"headers": { "Authorization": "Bearer YOUR_CIVITAI_API_KEY" }
}
}
}
Tip: hand your agent
https://mcp.civitai.com/llms.txt(or justhttps://mcp.civitai.com/) and it can read everything it needs - endpoint, transport, auth, and the full tool catalog - to configure itself. See The/llms.txttrick.
CLI (no MCP client needed)
Some agent runtimes can load a skill but can't edit their MCP client config (no way to add an HTTP MCP server). For those, pull the zero-dependency Node CLI (Node >=18) and drive the server straight from the shell - no MCP client config:
curl -fsSL https://mcp.civitai.com/cli -o mcp-cli.mjs
node mcp-cli.mjs list
node mcp-cli.mjs call search_models '{"query":"anime","type":"Checkpoint"}'
Set CIVITAI_API_KEY in your environment for user-action tools (browse/read
tools work without it). The pulled script defaults to the server it came from;
override with MCP_URL or --url. Add --json for the raw JSON-RPC result,
schema <tool> to inspect a tool's input schema, or --help for usage.
Self-host
Prefer to run your own instance (private deployment, in-cluster next to the
Civitai app, or local dev)? All three paths below work. The API destination is
fully overridable via CIVITAI_API_URL.
pnpm
pnpm install
cp .env.example .env # set CIVITAI_API_KEY for authenticated tools (optional)
pnpm build
pnpm start # HTTP mode on :3100, GET /healthz, POST /mcp
For local development with hot reload:
pnpm dev
Docker
Prebuilt images are published to GHCR:
docker run --rm -p 3100:3100 \
-e CIVITAI_API_KEY=YOUR_CIVITAI_API_KEY \
ghcr.io/civitai/civitai-mcp-server:latest
curl localhost:3100/healthz
Or build locally:
docker build -t civitai-mcp-server .
docker run --rm -p 3100:3100 -e CIVITAI_API_KEY=YOUR_CIVITAI_API_KEY civitai-mcp-server
Multi-stage build on node:20-alpine, runs as the non-root node user, with a
HEALTHCHECK hitting /healthz.
Kubernetes
See k8s/deployment.example.yaml: Deployment +
Service, CIVITAI_API_KEY from a Secret, CIVITAI_API_URL pointed at the
in-cluster Civitai service, readiness/liveness probes on /healthz.
Local stdio (for agents that spawn a process)
{
"mcpServers": {
"civitai": {
"command": "node",
"args": ["/path/to/civitai-mcp-server/dist/index.js"],
"env": {
"MCP_TRANSPORT": "stdio",
"CIVITAI_API_KEY": "YOUR_CIVITAI_API_KEY"
}
}
}
}
Environment variables
| Var | Default | Purpose |
|---|---|---|
CIVITAI_API_URL |
https://civitai.com |
Base URL for all API calls (REST + tRPC). In-cluster override, e.g. http://civitai-app:3000. |
CIVITAI_WEB_URL |
https://civitai.com |
Public website base for user-facing links in tool output (post/model/image/profile URLs). Distinct from CIVITAI_API_URL, which may point at an internal cluster service — links handed to users must stay public. The civitai.com default is correct for the hosted deployment; only override for civitai.red or a self-host on another domain. |
CIVITAI_API_KEY |
— | Bearer token for authenticated calls. Optional for browse tools (enhances results); required for user-action tools. In HTTP mode it is the fallback when a request omits an Authorization header. |
MCP_TRANSPORT |
http |
http (Streamable HTTP) or stdio (local dev / desktop MCP clients). |
PORT |
3100 |
HTTP listen port (http transport only). |
PUBLIC_BASE_URL |
— | Optional canonical public base URL (e.g. https://mcp.civitai.com). When set, the advertised MCP endpoint in the landing page / llms.txt uses this instead of deriving it from the request Host header. Set it on the canonical hosted deployment so it always advertises its public address even behind proxies that don't forward Host/X-Forwarded-* reliably. Default: unset (Host-derived). |
OAUTH_ENABLED |
false |
Gates one-click OAuth (RFC 9728 protected-resource metadata + the 401 WWW-Authenticate challenge). Off = token-only mode. Turn on only once Civitai's OAuth dynamic client registration is live. See OAuth one-click connect. |
CIVITAI_USER_ID |
— | Optional: skip the user.getToken JWT round-trip for self-id resolution. |
CIVITAI_UPLOAD_MAX_BYTES |
10485760 |
Max bytes accepted for an image fetched/decoded by upload_image (10 MB). Guards against memory exhaustion. |
CIVITAI_UPLOAD_ALLOWED_HOSTS |
— | CSV allowlist of hostnames permitted for URL-based image uploads. When set, only these hosts (and subdomains) may be fetched. Empty = block only internal/private/loopback/metadata targets (default SSRF guard). |
MCP_ALLOWED_HOSTS |
— | CSV of Host values allowed by DNS-rebinding protection. When set, protection is enabled and only these hosts may reach /mcp. Leave empty in-cluster (behind ingress); set for localhost dev, e.g. localhost:3100,127.0.0.1:3100. |
Per-request auth (multi-tenant)
In HTTP mode, if an incoming MCP request carries Authorization: Bearer <key>,
that key is used for upstream calls instead of CIVITAI_API_KEY. The env key is
the fallback. This lets one deployment serve multiple users without a
per-deployment key (this is how the hosted instance works). In stdio mode only
the env key is used.
Tool catalog (53 tools)
Every tool returns both a compact human-readable text block and a
structuredContent JSON payload. Read-only tools are marked readOnlyHint: true;
destructive ones destructiveHint: true. Errors are normalized to
{ ok: false, error, details? } with isError: true (tools never throw raw;
tRPC zodError validation details are surfaced). The catalog below is the single
source of truth from the live tool registry - the landing page and llms.txt
render the same list.
Browse (no auth required)
| Tool | Description |
|---|---|
search_models |
Search models. Includes AIR URNs + nextCursor. Client-side type-filter workaround for the Meilisearch query+type bug. |
get_model |
Batch model details (concurrency 3): versions, files, AIR URNs. |
get_model_version |
Batch version details: files, trigger words, AIR. |
search_images |
Search images with full generation metadata (withMeta=true). |
get_image |
Batch image details: prompt, negative, sampler, steps, CFG, seed, resources. |
search_creators |
Search creators/users. |
list_enums |
List filter enum values (model types, sorts, base models, timeframes). |
Posts (auth - onboarded, non-muted)
| Tool | Description |
|---|---|
create_post |
Flagship sharing flow via the composite post.createWithImages (ONE atomic call: create + ordered images + optional publish; server handles cleanup). Images by UUID or URL (auto-uploaded); sequential index. publishedAt returns as a Date. MediaWrite scope. |
get_post |
Fetch a post by ID. |
publish_post |
Publish a draft via post.update { publishedAt } (Date hint). |
delete_post |
Delete a post you own. |
Engagement (auth)
| Tool | Description |
|---|---|
react |
Toggle Like/Dislike/Laugh/Cry/Heart on image/post/article/comment/resourceReview/etc. reaction.toggle is fire-and-forget (200 != confirmed). Guarded. |
upsert_resource_review |
Star rating (1-5) + recommend + Markdown details. resourceReview.upsert. Guarded. |
get_my_resource_review |
Read your existing review for a model version. |
toggle_follow_user |
Follow/unfollow (resolves username to id). user.toggleFollow. Verified. |
toggle_favorite_model |
Favorite/bookmark a model (explicit setTo). user.toggleFavorite. |
notify_model |
Toggle new-version notifications. user.toggleNotifyModel. |
toggle_bookmark_article |
Bookmark/un-bookmark an article. user.toggleBookmarkedArticle. Verified. |
complete_onboarding_step |
Complete TOS/RedTOS/Profile/BrowsingLevels/Buzz. Prerequisite for any verified/guarded write. |
Articles (auth)
| Tool | Description |
|---|---|
upsert_article |
Create/update. Markdown to HTML. Cover by UUID or URL (auto-uploaded). |
publish_article |
getById to rebuild to upsert with the publishedAt: ['Date'] hint. Idempotent. |
unpublish_article |
Dedicated article.unpublish. |
get_article |
Fetch by ID. |
Comments (auth)
| Tool | Description |
|---|---|
list_comments |
Recursive thread fetch with per-comment reaction aggregation. |
get_comment |
Single comment, uncapped body. |
post_comment |
Post or reply (reply via parentCommentId to comment-entity chaining). |
edit_comment / delete_comment / react_to_comment |
Edit, delete, toggle reaction. |
pin_comment / lock_thread |
Moderator-gated upstream. |
Collections (auth - flag-gated collections)
| Tool | Description |
|---|---|
upsert_collection |
Create/update a collection. collection.upsert. Guarded. |
add_to_collection |
Save one item (article/image/post/model) into one or more collections. collection.saveItem (exactly one id field + collections[]). |
follow_collection |
Follow/unfollow a collection. |
Notifications (auth)
| Tool | Description |
|---|---|
list_notifications |
List notifications (unread/category filters). cursor: ['Date'] hint applied; returns nextCursor. |
mark_notifications_read |
Mark one (id as string, bigint hint) / all / a category read. |
check_notifications |
Quick unread count. user.checkNotifications. |
Messaging (auth)
| Tool | Description |
|---|---|
send_direct_message |
Lookup to chat.createChat to chat.createMessage. Markdown. Starts a NEW chat. |
Chat (auth) - read & reply to existing threads
| Tool | Description |
|---|---|
list_chats |
List your conversations + participants. |
get_chat_messages |
Read a chat's messages (paginated, nextCursor). |
reply_to_chat |
Send into an existing chat (chat.createMessage, Markdown, <=2000 chars). |
mark_chat_read |
Mark ONE chat read via chat.markChatRead { chatId } (advances lastViewedMessageId). |
mark_all_chats_read |
Blanket clear of every conversation via chat.markAllAsRead. |
Bounties (auth - flag-gated bounties)
| Tool | Description |
|---|---|
create_bounty |
Create via bounty.create (NOT bounty.upsert - blocked for API keys). startsAt/expiresAt Date hints; >=1 example image (UUID or URL). |
update_bounty |
Update a bounty you own. bounty.update. |
create_bounty_entry |
Submit an entry via the composite bountyEntry.submit (>=1 pre-uploaded file ref {url,name,sizeKB} + >=1 image UUID/URL). BountiesWrite scope. |
award_bounty |
Award a bounty to an entry. bountyEntry.award { id }. |
Announcements (auth, moderator)
| Tool | Description |
|---|---|
upsert_announcement |
Create/update with field merge. Image by UUID or URL. startsAt defaults to now on create. |
delete_announcement |
Delete by ID. |
list_announcements |
scope: current (live) or all (paginated, moderator). |
Changelog (auth, moderator + changelogEdit flag)
| Tool | Description |
|---|---|
upsert_changelog |
Create/update. Markdown to HTML; effectiveAt: ['Date'] hint. |
Images (auth)
| Tool | Description |
|---|---|
upload_image |
URL or base64 to presign to PUT to UUID (+ probed dimensions). |
Utility
| Tool | Description |
|---|---|
whoami |
Resolve the current user (id, username) and authoritative status via user.getSelfStatus: real isOnboarded + completedSteps, muted, isModerator, subscription tier. Good deploy smoke test. |
Auth & scopes
- API key: create one at civitai.com/user/account
and send it as
Authorization: Bearer <key>. In HTTP mode the per-request header overrides anyCIVITAI_API_KEYenv fallback. - Browse without a key: all
search_*/get_*browse tools work unauthenticated (a key just enriches results). - Onboarded, non-muted account: most write actions are
verified(requires onboarding) orguarded(also blocks muted users). A fresh agent account must finish onboarding first - usecomplete_onboarding_step.whoamireports the current onboarding/muted/moderator state. - Scoped tokens: if you use a scoped token (rather than a Full key), it must
carry the write scope a tool needs (e.g.
MediaWrite,SocialWrite,BountiesWrite,CollectionsWrite). A Full key passes everything. - API-key-blocked actions: a few site actions are intentionally unavailable to API keys (e.g. tipping buzz) and are not shipped as tools.
OAuth one-click connect (opt-in, off by default)
Off by default. Set
OAUTH_ENABLED=trueto turn this on. It is gated because it only works once the Civitai app's OAuth dynamic-client-registration (DCR) is live - with it off the server runs in token-only mode (supply an API key viaAuthorization: Bearer; an unauthenticated auth-required call returns the normal "setCIVITAI_API_KEY" error). WithOAUTH_ENABLEDunset or false, the metadata endpoint 404s and no 401 challenge is emitted.
The server is an OAuth 2.0 resource server (RFC 9728 / RFC 6750). When
OAUTH_ENABLED=true and the Civitai app's DCR is live, MCP clients can connect
with no manually pasted API key - they discover the authorization server and run
the standard authorization-code flow on first use:
- The client
POSTs atools/callfor an auth-required tool without anAuthorizationheader. - The server replies HTTP 401 with
WWW-Authenticate: Bearer resource_metadata="<base>/.well-known/oauth-protected-resource". - The client fetches
GET /.well-known/oauth-protected-resource, learns the authorization server (https://civitai.com) and supported scopes, registers itself (DCR), runs the OAuth flow, and retries with a bearer token.
Two behaviors back this:
GET /.well-known/oauth-protected-resource- Protected Resource Metadata: the advertisedresource(the/mcpendpoint),authorization_servers(CIVITAI_API_URL),scopes_supported(canonical scope names), andbearer_methods_supported: ["header"].- 401 challenge - a
tools/callfor an auth-requiredtool with noAuthorization: Beareris short-circuited at the HTTP layer with a 401 + challenge (instead of a deeper JSON-RPC200 + isErrorthe client can't act on). Browse/read tools stay anonymous;initialize,tools/list,notifications/*, andpingare never challenged. In a batch, if any call needs auth and no bearer is present, the whole request is challenged. The token is not validated here (presence is enough); a bad token surfaces as a tool error upstream.
Prod note: set
PUBLIC_BASE_URL=https://mcp.civitai.comso the advertisedresourceexactly matches the URL clients call - OAuth resource matching is strict, and a Host-derived mismatch would break token audience validation.
The /llms.txt trick
The server exposes an llms.txt-style self-setup guide. Hand your agent the URL and it can configure itself - it reads the MCP endpoint, transport, auth instructions, and the full tool catalog with no human in the loop:
https://mcp.civitai.com/llms.txt
https://mcp.civitai.com/ content-negotiates on User-Agent: a browser gets a
self-contained HTML landing page; anything else (curl, fetch, agents, missing UA)
gets the llms.txt text. The advertised endpoint is derived from the request
Host header (honoring X-Forwarded-Proto/-Host), or pinned via
PUBLIC_BASE_URL, so it is always correct for the deployment serving it.
Transports
- Streamable HTTP (default):
POST /mcp. Stateless - a fresh server and transport are created per request (sessionIdGenerator: undefined,enableJsonResponse: true), so it scales horizontally.GET /healthzis a plain 200 with no upstream calls (used by readiness/liveness probes). - stdio: set
MCP_TRANSPORT=stdio. stdout is reserved for the protocol; logs go to stderr.
HTTP endpoints
| Method & path | Purpose |
|---|---|
POST /mcp |
MCP Streamable HTTP endpoint (JSON-RPC). |
GET /healthz |
Plain 200 liveness/readiness probe (no upstream calls). |
GET / |
Dual-audience index: browser UA gets HTML; everything else gets llms.txt as text/plain. |
GET /llms.txt |
The agent self-setup guide as text/plain. |
GET /cli |
The pullable zero-dependency Node CLI (mcp-cli.mjs), with this server's /mcp endpoint baked in. For runtimes that can't add an MCP server to their config. |
Security
- SSRF guard:
upload_imagefetches user-supplied URLs through a guard that blocks internal/private/loopback/metadata targets by default, with an optionalCIVITAI_UPLOAD_ALLOWED_HOSTSallowlist. - Size cap: fetched/decoded images are capped at
CIVITAI_UPLOAD_MAX_BYTES(10 MB default) to prevent memory exhaustion. - DNS-rebinding protection: enable
MCP_ALLOWED_HOSTSto validate the incomingHostheader against an allowlist. - Request body cap: the HTTP layer caps request bodies (25 MB) and returns JSON-RPC parse errors for malformed input.
- Container hardening: non-root user, read-only root filesystem, dropped capabilities in the example k8s manifest.
Local dev with the MCP Inspector
pnpm inspector # runs: npx @modelcontextprotocol/inspector tsx src/index.ts
This launches the Inspector against the stdio transport. Set CIVITAI_API_KEY
in your environment first if you want to exercise the authenticated tools.
Development
pnpm typecheck # strict tsc, no emit
pnpm test # vitest (no network - fetch is mocked)
pnpm build # tsc to dist
See CONTRIBUTING.md for the contribution flow.
Non-goals
- No generation tools (generation lives elsewhere).
- No moderation actions.
- No model file downloads.
License
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.