imogen
Allows signing in to imogen through Authentik as an OIDC provider, with optional group-based administrator assignment and account linking by verified email.
Allows signing in to imogen through Google as an OIDC provider.
Allows signing in to imogen through Keycloak as an OIDC provider, with optional group-based administrator assignment and account linking by verified email.
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., "@imogenfind photos from my trip to Yosemite and add them to a new album"
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.
imogen is a self-hosted photo and video library for a home lab. It holds your photographs on hardware you control, and it opens them to whatever you want to build: a web interface, a REST API, a TypeScript SDK for a mobile app, and an MCP endpoint so your AI assistants can search the library too.
Justified timeline — photos keep the proportions they were shot in, grouped by day
Everything a camera produces — HEIC, RAW, JPEG, video, Live Photos
Installable — the web interface is a PWA and works offline
Two ways to sign in — local accounts, or single sign-on through Authentik, Keycloak, Google, or anything else that speaks OIDC
Built to be built on — OpenAPI, an SDK, and an OAuth 2.1 server, so a third-party app is a first-class citizen rather than an afterthought
People — optional face grouping, running entirely on your own server
A vault — photos that need a passphrase to see, hidden from everything else
Sharing — publish an album or a single photograph as a link, with an optional password, an expiry date, and downloads on or off
Administration — invite people, suspend accounts, watch the processing queue, disconnect apps, and see everything that is currently public
Agent-ready — connect Claude or Grok to your library with a URL
Running it
curl -O https://raw.githubusercontent.com/ergofobe/imogen-server/main/docker-compose.yml
docker compose up -dOpen http://localhost:3000. The first account you create becomes the administrator.
That is the whole installation. imogen needs Postgres, and the compose file brings one up; there is no message broker, no cache, and no sidecar to run.
Configuration
Everything is an environment variable, validated at start-up — the server refuses to boot on a bad configuration rather than failing later under load.
Variable | Default | What it does |
|
| The URL people reach imogen at. OAuth and share links are built from it, so it must be correct behind a reverse proxy. |
| — | Postgres connection string. Required. |
|
| Where photographs live. Back this up. |
| generated | Signs sessions. Generated and persisted on first run if unset. |
|
| Whether anyone may create an account. The first account is always allowed. A starting value only — an administrator can change this in the app, and what they set wins. |
|
| How long deleted photos are recoverable. Also a starting value that an administrator can change. |
|
| Photos processed at once. Raise it on a machine with cores to spare. |
Administration
The first account created becomes the administrator. Its Settings page has a link
through to /admin, which is where accounts, invitations, the processing queue,
connected applications, storage and share links are managed.
The area is not merely closed to everyone else — it answers a plain 404, identical to the one the server gives for a path it has never heard of, so it cannot be found by looking for it. Anything scanning for an administration panel is told nothing.
To add somebody to a closed server, make an invitation and send them the link. The link is shown once and stored only as a hash, so if it is lost, revoke it and make another.
Single sign-on
Point imogen at any OIDC provider. Set the redirect URI in your provider to
https://photos.example.com/api/v1/auth/oidc/callback.
IMOGEN_OIDC_ISSUER: https://auth.example.com/application/o/imogen/
IMOGEN_OIDC_CLIENT_ID: ...
IMOGEN_OIDC_CLIENT_SECRET: ...
IMOGEN_OIDC_LABEL: Sign in with Authentik
IMOGEN_OIDC_ADMIN_VALUE: imogen-admins # members of this group become administrators
IMOGEN_OIDC_ACCOUNT_URL: '' # optional; guessed for Authentik and KeycloakExisting local accounts are linked by verified email address, so turning on SSO does not strand anyone.
The provider owns the name and email of the accounts it manages: imogen re-reads them at
every sign-in, shows them read-only in settings, and links out to the provider's own
account page. Set IMOGEN_OIDC_ACCOUNT_URL if that link needs to point somewhere other
than the guess.
Administrator status follows IMOGEN_OIDC_ADMIN_VALUE when you set it — including
removing it when someone leaves the group. Leave it unset and imogen never touches roles,
so an administrator promoted locally stays one.
Behind a reverse proxy
imogen serves plain HTTP and expects to sit behind something that terminates TLS. Pass
through X-Forwarded-For so sessions record a sensible address, allow large request
bodies for video uploads, and set IMOGEN_PUBLIC_URL to the external URL.
photos.example.com {
reverse_proxy localhost:3000
request_body { max_size 8GB }
}Related MCP server: immich-mcp
People
imogen can find faces and group the photos each person appears in, so you can name someone once and then browse everything they are in. Detection and recognition run on your server; no photograph is sent anywhere.
It is off until you turn it on, from the People page. Enabling it downloads about 190 MB of recognition models and scans your existing library in the background.
Photos in your vault are never scanned, and vaulting a photo forgets the faces already found in it.
Nobody is named until you name them. Unnamed groups are shown so you can name them, and either can be hidden.
Grouping errs toward splitting a person across two groups rather than merging two people into one. Select several and tell it they are the same person.
A note on the models. imogen uses InsightFace's SCRFD and ArcFace, which are licensed for non-commercial research use. imogen ships no models: your server downloads them when you enable the feature, so the licence decision stays with you. If that does not suit your situation, leave the feature off.
The vault
Some photographs should not be one careless scroll away. Move them to the vault and they leave the library completely: not the timeline, not search, not your albums, not a shared link, and not anything an AI assistant can see.
Opening it needs a passphrase, entered again even though you are already signed in.
A few decisions worth knowing about:
The passphrase is not your account password. Single sign-on accounts have no local password, and more to the point, a session that is already signed in should not be enough — finding your laptop open should not open this too.
Only a browser session can open it. An API token or an MCP connector can hold perfectly valid credentials and still have no way in. That is by construction, not by omission.
It closes itself after fifteen minutes, or immediately when you ask it to.
Nobody can reset it for you. There is no recovery path, which is the point.
Moving a photo into the vault also removes it from every album, since an album is something you can share.
Connecting an AI assistant
imogen speaks MCP, so an assistant can search your library, look at a photo, and manage albums — with your permission and nothing else.
Claude.ai or Grok: add a connector pointing at https://photos.example.com/mcp.
Nothing is pasted by hand: the client discovers imogen, registers itself, and sends you
to a consent screen that names exactly what it is asking for. Revoke it any time from
settings.
A local agent (Claude Code, or anything that speaks MCP over stdio):
bun add -g @imogen/mcp
imogen-mcp login --server https://photos.example.com{ "mcpServers": { "imogen": { "command": "imogen-mcp" } } }What an assistant can do
Tool | Permission |
|
|
|
|
|
|
|
|
Every tool is scoped to the connected account. There is no tool that deletes anything, and nothing in the vault is visible to any of them. Only people you have named are findable — unnamed groupings and hidden people are not.
Building on it
The API is documented at /api/v1/docs, with the OpenAPI 3.1 description at
/api/v1/openapi.json.
There are clients for five languages in imogen-sdk — TypeScript, Rust, Python, Swift and Kotlin. In TypeScript:
bun add @imogen/sdkimport { ImogenClient } from '@imogen/sdk'
const imogen = new ImogenClient({ baseUrl: 'https://photos.example.com', token })
const page = await imogen.assets.list({ q: 'harbour', limit: 50 })
for await (const asset of imogen.assets.iterate()) console.log(asset.originalFilename)
// Picks its protocol by size: one request for photos, a resumable session for video.
await imogen.assets.uploadMany(files, {
onFileComplete: (outcome, done, total) => console.log(`${done}/${total}`),
})Writing a mobile app
Every SDK ships the OAuth client a native app needs — the Swift and Kotlin ones are there for exactly this. Nothing is hard-coded: the app registers itself, so it works against any imogen server its user points it at.
import { OAuthClient } from '@imogen/sdk'
const oauth = new OAuthClient('https://photos.example.com')
const client = await oauth.register('My Photo App', ['myapp://oauth'])
const pending = await oauth.beginAuthorization(client.client_id, 'myapp://oauth')
// Open pending.authorizationUrl in the system browser, then on the callback:
const tokens = await oauth.completeAuthorization(pending, callbackUrl)Uploads are idempotent by content: re-sending a photo the server already has returns
the existing asset instead of storing a second copy, so a sync loop can be simple and
still be correct. Pass deviceAssetId and a client knows what it has already sent
without keeping its own ledger.
Pairing, instead of asking for a hostname
The flow above still needs the app to know which server to talk to, and a self-hosted library is at whatever address its owner chose. Typing that on a phone keyboard is the worst moment in installing one of these apps, so the browser does it instead.
Settings → Devices → Pair a device makes a one-time ticket and renders it as a QR code carrying both the server URL and the code. The app reads the square and does the rest:
val invitation = parsePairingUri(scanned) ?: return
val oauth = OAuthClient(invitation.serverUrl)
val paired = oauth.pair(invitation.code, "imogen for Android", "imogen://oauth", Build.MODEL)What crosses the camera is a ticket, not a token. It is single-use, lives five minutes, and buys exactly one authorization code — bound to a PKCE challenge that never left the device, so a photograph of somebody's screen is not enough. The grant that comes out is an ordinary one, and appears under connected applications like any other.
The same page offers the ticket as a link, for a phone already reading the web interface: tapping it opens the app directly.
Developing
git clone https://github.com/ergofobe/imogen-server
cd imogen-server
bun install
docker compose -f docker/compose.dev.yml up -d # Postgres
export DATABASE_URL='postgres://imogen:imogen@localhost:5432/imogen'
bun run db:migrate
bun run dev # API on :3000
bun run dev:web # web on :5173, proxying to the APIbun test # needs the dev Postgres running
bun run typecheck
bun run lintTests run against a real Postgres and a real HTTP server rather than mocks. The parts worth getting right — the OAuth flows, cursor pagination, the media pipeline — are exactly the parts a mock would let you get wrong.
Layout
Package | What it is |
| Hono app: routes, auth, media pipeline, job workers. |
| The React PWA. It consumes |
| The stdio bridge for local agents. |
The client libraries live in their own repository,
imogen-sdk — TypeScript, Rust, Python, Swift and
Kotlin, checked against one shared set of contract fixtures. @imogen/shared, the Zod
schemas this server validates against and generates its OpenAPI document from, lives there
too: it is the API contract, and the contract belongs with the clients that have to keep to
it.
packages/server/src/api/sdk-contract.test.ts is this side of that arrangement. It stands
up a real app and drives it through the published TypeScript client, which is the only
place the two halves can be shown to agree.
The design document is in docs/superpowers/specs.
Not there yet
Semantic search — finding a photo by describing it — is not implemented. The schema reserves a vector column on assets and the search index is in place, so it can arrive without a migration, but today search covers filenames, descriptions, places, camera metadata, and the people you have named.
Also absent: reverse geocoding (coordinates are shown as coordinates), video transcoding, and S3 storage. The storage driver is an interface, so S3 is a contained change when someone wants it.
Face grouping works but has rough edges worth knowing about. It reads frontal, reasonably lit faces well; profiles, sunglasses, motion blur, and young children — whose faces change faster than a stored average can follow — are where it will disappoint you. It errs toward splitting one person across two groups rather than merging two people, on the grounds that the first is a click to fix and the second files somebody's photographs under another person's name.
Licence
AGPL-3.0-or-later. If you run a modified imogen as a service, share the modifications.
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 Connectors
Holiday photo MCP server: list and fetch personal holiday photos inline in Claude chat.
Persistent memory for AI assistants — one shared, OAuth-secured vault for every MCP client.
Automate 1,000+ services from any MCP-compatible AI agent: build Applets, run actions and queries.
AI-powered image processing via GPU. Remove backgrounds and upscale images (2x/4x) directly from any MCP client. OAuth 2.1 authenticated, returns processed images inline with download links. Free credits on signup at maskr.io.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to search, browse, and retrieve metadata and images from your Google Photos library. It supports content-based filtering, album listing, and location extraction via STDIO and HTTP transports.39
- FlicenseAqualityBmaintenanceAn MCP server for Immich self-hosted photo management that provides AI-accessible tools for browsing, searching, organizing, and managing photo libraries with duplicate detection and safe deletion workflows.431

CoreViz MCPofficial
AlicenseNot gradedqualityDmaintenanceExposes a visual library with semantic search, tagging, editing, and management of photos as tools for AI agents like Claude Code.3048MIT- FlicenseNot gradedqualityCmaintenanceAn MCP server that integrates AI assistants with the Flickr API, enabling management of photos, albums, groups, and contacts via natural language commands.1
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/ergofobe/imogen-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server