homebox-mcp
homebox-mcp
An MCP server for a self-hosted
Homebox inventory instance — specifically the
actively-maintained sysadminsmedia/homebox
fork's current API (entities/tags), not the older, archived hay-kot/homebox
API (items/labels/locations) that most existing docs and search results
still describe. It covers every read and write endpoint: items,
locations, tags, notifiers, maintenance entries, attachments, users, group
settings/members/invitations, statistics, bulk actions, QR codes, and CSV
import/export.
This is a plain MCP server with no bundled chat front end — point any
MCP-capable client at it: Claude Desktop, Claude Code, or a bot like
ocabra_telegram, which
uses this server's tools to give natural-language, tool-calling access to
your inventory over Telegram. Defaults to stdio (a client spawns this
process directly); set MCP_TRANSPORT=http to serve MCP over Streamable
HTTP as an always-on service instead, for a client on a different
host/container — see "Running as an HTTP service" below.
Contents
How it fits together
┌───────────────────────┐
│ src/tools/*.ts │
│ one definition per │
│ Homebox endpoint │
└───────────┬────────────┘
▼
┌───────────────────────┐
│ src/mcp/server.ts │
│ MCP server (stdio or │
│ Streamable HTTP) │
└───────────┬────────────┘
▼
┌───────────────────────┐
│ src/homebox/client.ts │
│ login, token refresh, │
│ generic REST calls │
└───────────┬────────────┘
▼
your Homebox instanceEvery tool is defined once, in src/tools/, as
{ name, description, write, zod-shape, handler }, and registered with the
MCP server in src/mcp/server.ts.
For request lifecycles, authentication boundaries, and extension points, see the architecture guide.
Prerequisites
A current
sysadminsmedia/homeboxinstance that exposes the/api/v1/entitiesAPI. The archivedhay-kot/homeboxAPI is not supported.A dedicated Homebox account for the MCP server. Its Homebox permissions still apply in addition to this server's
READONLYsetting.Node.js 22 and npm for a source install, or Docker with Docker Compose for the container install.
An MCP client. Running this process in a terminal does not provide a chat interface.
Choose a transport
Situation | Transport | What the client connects to |
The MCP client can spawn a local process |
|
|
The client is on another host or in another container |
|
|
Docker Compose deployment |
| The published port and |
Use stdio when possible: it needs no listening port or MCP bearer token. Use
HTTP when the process boundary makes stdio unavailable, and set
MCP_AUTH_TOKEN before exposing the listener.
Setup
git clone <repository-url> homebox_mcp
cd homebox_mcp
cp .env.example .env
# set HOMEBOX_URL, HOMEBOX_USERNAME, and HOMEBOX_PASSWORD in .env
npm ci
npm run buildKeep READONLY=Y for the first connection. After confirming the client can
list and retrieve inventory, change it to N only if write operations are
required, then restart the server.
Environment variables (.env)
Variable | Required | Description |
| yes | Base URL of your Homebox instance, with no |
| yes | Login for a Homebox account the agent uses. Create a dedicated user rather than reusing your own. |
| yes |
|
| no |
|
| no | Only used when |
| no | Only used when |
READONLY is enforced at the tool-registration layer: when READONLY=Y,
write tools (items_create, items_delete, locations_update, …) are
never registered with the MCP server, so a connected client — or a model
driving it — cannot call them regardless of what it's asked to do. Toggle
it in .env and restart the server to change modes.
Who is allowed to talk to this server at all (e.g. which Telegram user)
is not this server's concern — that's the client's decision (whatever
spawns it over stdio, or whatever holds MCP_AUTH_TOKEN over HTTP).
See ocabra_telegram's HOMEBOX_ADMIN_TELEGRAM_ID.
Running
npm run build
node dist/index.jsBy default the process speaks MCP over stdio, meant to be spawned by a
client rather than run standalone. Set MCP_TRANSPORT=http (see
.env.example) to run it as an always-on HTTP service instead.
The startup message is written to stderr so it does not corrupt the MCP protocol on stdout. In stdio mode, seeing the process wait without printing a prompt is expected: it is waiting for an MCP client to send protocol messages.
Running as an HTTP service
MCP_TRANSPORT=http MCP_AUTH_TOKEN=$(openssl rand -hex 32) node dist/index.jsThis is the mode to use whenever the client (e.g. ocabra_telegram)
doesn't run on the same host — a stdio-piped subprocess can't cross that
boundary the way plain HTTP does. MCP_HTTP_HOST (default 0.0.0.0),
MCP_HTTP_PORT (default 8765), and MCP_HTTP_PATH (default /mcp)
control the listener; MCP_AUTH_TOKEN gates every request behind a
matching Authorization: Bearer <token> header (constant-time compared)
— set it unless you've deliberately decided the network path here is
trusted on its own, since an unset token on a reachable port lets anyone
who can reach it drive every tool this server exposes (Homebox reads, and
writes unless READONLY=Y). The HTTP transport is stateful: an
initialize request creates one McpServer/transport pair for that
client session, keyed by the Mcp-Session-Id the transport assigns and
the client echoes back on every subsequent request, and it's torn down
when the client sends DELETE or the connection closes. A client that
reuses one session across many sequential tool calls (the normal case —
e.g. ocabra_telegram's tool-calling loop) needs this: an earlier
per-request design, with a fresh pair for every single request, could tear
a transport down while an adjacent request in the same session still had
an SSE response in flight, which surfaced to MCP clients as the official
SDK's own "SSE stream ended without a response" even though this server
had already sent a complete, successful reply.
Tool-call responses are also sent as a single complete JSON body rather
than an SSE stream (enableJsonResponse: true). Every tool call here is
one request producing one response, so there's nothing to gain from a
long-lived stream — and streaming has a real cost: the reference MCP
client only resumes an interrupted SSE response when the stream carried a
resumable event ID (which needs an eventStore, not configured here), and
otherwise gives up immediately with "SSE stream ended without a response"
even on a response this server had already completed successfully. Larger
responses (base64-encoded photos and other attachments in particular)
spend longer as an open stream and were disproportionately exposed to
that gap; a single JSON body removes the "cut off partway through" window
entirely.
On successful startup, stderr includes the listener address, number of registered tools, readonly mode, and whether HTTP authentication is enabled:
homebox-mcp: MCP server ready over HTTP on 0.0.0.0:8765/mcp (65 tools, READONLY=N, auth=on)The /mcp route is an MCP endpoint, not a conventional browser page or
health-check URL. GET /mcp intentionally returns 405; connect with an MCP
Streamable HTTP client using POST instead. A 401 response means the bearer
token is absent or does not exactly match MCP_AUTH_TOKEN.
Client configuration
Claude Desktop / Claude Code
{
"mcpServers": {
"homebox": {
"command": "node",
"args": ["/absolute/path/to/homebox_mcp/dist/index.js"],
"env": {
"HOMEBOX_URL": "http://localhost:7745",
"HOMEBOX_USERNAME": "agent@example.com",
"HOMEBOX_PASSWORD": "change-me",
"READONLY": "Y"
}
}
}
}The path in args must be absolute because GUI clients often start with a
different working directory. Restart the MCP client after changing its
configuration or rebuilding the server. Environment variables configured in
the client take precedence over relying on a project-local .env whose
location may not be the client's working directory.
ocabra_telegram
ocabra_telegram's bot.py drives this server with OpenAI-style
tool-calling against its Ocabra backend, over MCP's Streamable HTTP
transport (the official mcp Python SDK client) — not a spawned
subprocess, so this server can run on the same host, a different host on
your LAN, or its own container.
Run this server with
MCP_TRANSPORT=httpand aMCP_AUTH_TOKENset (see "Running as an HTTP service" above, or "Docker" below).In
ocabra_telegram's.env:HOMEBOX_MCP_ENABLED=true HOMEBOX_MCP_URL=http://homebox-mcp-host:8765/mcp HOMEBOX_MCP_AUTH_TOKEN=same-token-as-MCP_AUTH_TOKEN-above HOMEBOX_ADMIN_TELEGRAM_ID=123456789HOMEBOX_MCP_URL/HOMEBOX_MCP_AUTH_TOKENmust match what this server is actually serving (MCP_HTTP_HOST/PORT/PATH/MCP_AUTH_TOKENabove) — the exact same secret goes on both sides.
See that repo's README, "Homebox Integration", for the full list.
Docker
cp .env.example .env # fill in HOMEBOX_URL/USERNAME/PASSWORD, MCP_AUTH_TOKEN, ...
cp docker-compose.yml.sample docker-compose.yml
docker compose up -d --builddocker-compose.yml is gitignored (like .env) precisely so a later
git pull never clobbers anything you change in it — edit your own copy
freely; docker-compose.yml.sample is the tracked template to re-diff
against if this project changes it.
The image runs this server with MCP_TRANSPORT=http and restart: always, publishing port 8765 (see docker-compose.yml to change it or
bind to a specific interface).
Follow startup, MCP request, tool-call, authentication, Homebox API, and error logs with:
docker compose logs -f homebox-mcpActivity log lines include an ISO timestamp and useful request status and duration fields. Tool arguments, request bodies, passwords, and tokens are not logged. Logs are written to stderr so they cannot interfere with MCP's stdio protocol.
Inside Compose, HOMEBOX_URL=http://homebox:7745 works only when a service
named homebox is reachable on a shared Docker network. If Homebox runs on
the Docker host or elsewhere, set HOMEBOX_URL to an address that is
reachable from the container, not necessarily the address used by your
browser.
Tool coverage
65 MCP tools, covering Homebox's current /v1/entities + /v1/tags API:
Items — list/search, get, create, update, patch, delete, breadcrumb path, custom fields, CSV import/export, attachments (add/download/update/ delete, plus external/link attachments), multilingual alternate-name and inventory-tag search, maintenance log (list/create/ update/delete, plus an all-items maintenance query)
Locations — list, tree, get, create, update, delete. Homebox has no separate "locations" resource anymore — a location is an entity whose entity type has
isLocation: true;locations_list/locations_treeuse/v1/entities/tree, which is still location-scoped.Tags — list, get, create, update, delete (called "Labels" in the archived Homebox API)
Entity types — list (the built-in "Item"/"Location" types plus any custom templates, and their
isLocationflag)Notifiers — list, create, update, delete, test
Users — get/update/delete self, change password, register (via group invitation)
Group — get/update settings, invitations (list/create/delete), members (list/remove), statistics (overall, by tag, by location, purchase price over time)
Bulk actions — ensure asset IDs, ensure import refs, set primary photos, zero item time fields, create missing thumbnails
Misc — server status, currency list, asset-ID lookup, QR code generation, bill-of-materials report
See src/tools/*.ts for the exact input schema of each tool.
For a complete, browsable list of tool names and access levels, see the
tool reference.
items_attachment_get returns attachment bytes directly in the MCP response:
photos use MCP image content so capable clients can display them, while PDFs
and other documents use embedded MCP resource content. Use the attachment IDs
included in an items_get response to request a particular file.
Homebox's download endpoint sometimes serves a photo as generic
application/octet-stream even though the attachment's own metadata records
an image/* MIME type. Since MCP clients only render a native image
content block when the type starts with image/, items_get (with
includeAttachments), items_photo_get, and items_attachment_get all
restore the attachment's declared image MIME type on the binary they return
before it reaches resultToContent, so a photo still displays as an image
instead of silently falling back to an embedded resource block.
For the common “show/send me a photo” workflow, call items_photo_get with the
item ID. It selects the primary photo (or the first photo if none is primary)
and returns the bytes as native MCP image content without requiring the caller
to inspect attachment metadata or provide an attachment ID. Clients should
forward/render that native image block directly; they must not replace it with
a Markdown attachment: URL, since those host-local references do not work
across chat bridges such as Telegram.
For a single-call alternative, pass includeAttachments to items_get with
photos, documents, or all. The response retains the item's JSON details
and appends each selected attachment as native MCP content. Omit the option to
avoid downloading attachment bytes.
Tool names use a <resource>_<operation> convention such as items_list,
items_get, and items_create. Write tools are also prefixed with [write]
in their MCP descriptions. With READONLY=Y, write tools are omitted from
tool discovery entirely rather than being exposed and rejected later.
Finding items by meaning and tags
Homebox's free-text entity search does not reliably find an item merely because it has a semantically related tag. For complete natural-language searches, use this two-step flow:
Call
tags_listand compare the available tag names with the user's intent. This is semantic discovery performed by the MCP client, so it works with inventory-specific vocabulary rather than a fixed synonym table.Call
items_listwith the original text inqand the IDs of all relevant tags inrelatedTagIds. For example, a search for “motorbike” can include the ID of a tag namedMotorcycle.
items_list runs each related tag as an additional search and unions those
results with all text/alternate-name results. This is intentionally different
from tags, which is a strict filter applied to text searches. Use
relatedTagIds to broaden recall; use tags when the user explicitly asks to
limit results to particular tags. If only tag names are available, pass them in
tagNames and the server will resolve them first.
The response includes searchTerms, matchedTags, and searchedTagIds so an
MCP client can explain which expansions and tags contributed to the results.
Duplicate entities found through multiple routes are returned only once.
For fields that Homebox's normal text endpoint does not index, items_list
also offers an opt-in deepSearch. It can search every scalar item value or
apply structured filters to fields such as purchaseFrom, purchasePrice,
sale and warranty values, dotted paths, and named custom fields. Deep search
reads every candidate item's complete details, so reserve it for queries that
cannot be answered by the faster normal text and tag search. Numeric comparison
operators also work with ISO dates, includeArchived expands the scan to
archived items, and the result's scanned count reports its scope.
Security checklist
Before enabling write tools or exposing the HTTP transport, verify all of the following:
Use a dedicated Homebox account and grant it only the permissions the client needs.
Start with
READONLY=Y. Changing it toNexposes all write tools, including permanent deletion, account deletion, member removal, and inventory-wide bulk actions.Set a long, randomly generated
MCP_AUTH_TOKENfor HTTP mode. Do not reuse the Homebox password as this token.Restrict the published port with a firewall, private network, VPN, or a specific Docker bind address. The bearer token protects requests, but does not encrypt traffic; use a trusted network or a TLS-terminating reverse proxy when requests cross an untrusted network.
Do not commit
.envordocker-compose.yml; both are ignored so local credentials and deployment-specific settings stay out of version control.Treat logs and tool results as potentially sensitive because inventory names, values, attachments, and user details may be returned to the MCP client.
Potential improvements
See docs/IDEAS.md for a prioritized list of possible testing,
operability, security, packaging, and tool-UX improvements. The document is a
roadmap, not a commitment that every idea will be implemented.
A note on Homebox API versions
Homebox's original repo (hay-kot/homebox) was archived; active development
continues at sysadminsmedia/homebox, which reorganized the API somewhere
around mid-2025: items and locations merged into a single generic
entities resource (distinguished by an entityTypeId, with locations
being entities whose type has isLocation: true), and labels was renamed
to tags. If you're running an old, unmaintained fork still on the
items/locations/labels API, this version of homebox_mcp will not work
against it — every call will 404. Check what your instance actually serves
with curl http://<your-homebox>/api/v1/status (always works, no auth) and
curl http://<your-homebox>/api/v1/entities vs .../api/v1/items (with a
valid Bearer token) to see which one responds instead of 404.
Troubleshooting
The client shows no Homebox tools
Run
npm run buildand confirmdist/index.jsexists.Use an absolute script path in stdio client configuration.
Check the MCP client's own logs for process startup errors.
Run
node dist/index.jsfrom the project directory to surface invalid environment values. Stop it with Ctrl+C after the startup message appears.
Homebox returns 401
This is authentication between this server and Homebox, not HTTP transport
authentication. Confirm HOMEBOX_USERNAME and HOMEBOX_PASSWORD, verify the
account can sign in to the same instance, and ensure HOMEBOX_URL points to
that instance. The server refreshes an expired Homebox token automatically and
retries one failed request.
The MCP endpoint returns 401
Send Authorization: Bearer <token> where <token> exactly matches
MCP_AUTH_TOKEN. This token protects access to MCP; it is separate from the
Homebox login and should not be set to the Homebox password.
Requests return 404
For Homebox API calls, confirm the instance uses the current
entitiesandtagsAPI described in A note on Homebox API versions.For MCP requests, confirm the client URL path matches
MCP_HTTP_PATH(default/mcp).A
404/400 invalid route keyspecifically on/v1/entities/<id>...from a tool call (items_get,items_photo_get,items_attachment_get, ...) is usually the model mistyping or fabricating a UUID it retyped from memory across several tool calls instead of reusing one verbatim. The client detects this, fuzzy-matches the bad id against the real entity list, and appendsDid you mean "<id>" ("<name>")?to the error when a close match exists, so the model self-corrects on its next call instead of retrying blindly (seesrc/homebox/similarity.ts). If the error instead says no close match was found, it's likely a genuinely wrong/deleted id rather than a typo.
Write tools are missing
This is expected when READONLY=Y. Set READONLY=N and restart the process
only after reviewing the credentials and network access available to the MCP
client.
Development
npm ci
npm run dev # run src/index.ts in watch mode
npm run typecheck # validate TypeScript without emitting dist/
npm run build # compile the production JavaScript into dist/If the compiler reports that it cannot find declarations for a dependency
(for example, TS7016 for express), restore the complete lockfile-defined
dependency tree before building:
npm ci
npm run buildType declarations are development dependencies, so an install created with
--omit=dev is suitable for running dist/, but not for compiling the
TypeScript sources.
There is currently no automated test suite. Before submitting a change, run
both npm run typecheck and npm run build. Tool implementations live in
src/tools/; the shared Homebox HTTP client lives in src/homebox/client.ts,
and transport registration lives in src/mcp/server.ts.
See CONTRIBUTING.md for the repository layout, tool
definition conventions, validation checklist, and guidance for keeping the
tool reference synchronized.
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/luisriverag/homebox_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server