HiveMCP
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., "@HiveMCPCreate a PowerPoint deck summarizing Q3 sales data"
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.
Generates and edits PowerPoint, Word, Excel and Markdown files for OpenWebUI. Runs as a container, exposed both as an MCP Streamable HTTP server and as an OpenAPI tool server from the same FastAPI app.
Why both: OpenWebUI renders inline iframes only for tool results carrying
Content-Disposition: inline, and that path exists for OpenAPI tool servers but not for
the native MCP surface. MCP gives protocol compatibility, OpenAPI gives the configuration
GUI.
What it does
Tool | Who can use it |
| everyone |
| everyone |
| everyone |
| everyone |
| everyone |
| administrators |
| everyone |
| everyone |
Markdown takes the same DocSpec as Word. The block types already describe a document,
so a second spec would only be a second thing for a model to learn and for this repository
to keep in step.
How finished files reach the user is a setting, HIVE_DELIVERY_MODE: uploaded to the
caller's own OpenWebUI files, handed over as a signed link from this server, or both
(the default, and the only mode where a failed upload still yields the document).
Related MCP server: office-mcp-server
Status
Milestone | Scope | State |
M0 | Integration spikes: session auth, file ownership, Rich UI | done |
M1 | Skeleton: config, auth, health, download, Dockerfile | done |
M2 | Render core: pptx / docx / xlsx from a spec | done |
M3 | MCP + OpenAPI surfaces, OpenWebUI file delivery | done |
M4 | Templates: admin-curated pool, upload, inspect | done |
M5 | Configuration GUI as an iframe | done |
M6 | Editing files uploaded to the chat | done |
M7 | Skill, K8s manifests, hardening | done |
Since M7: configurable delivery, Markdown as a fourth format, and cards that follow OpenWebUI's theme live.
Quick start
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
cp .env.example .env
make test # runs the suite in the container, no host Python needed
python -m hivemcp # http://localhost:8080/healthzmake test builds the test stage of the Dockerfile and runs pytest inside it, so the
suite executes against the same interpreter and the same pinned dependencies as
production. make test ARGS="-k templates -x" passes arguments straight through.
make test-host runs it locally instead, if you have the dev extras installed.
Docker
make up # build, start, wait for healthy
make smoke # verify it actually works
make logs
make downmake owui additionally starts an OpenWebUI instance on http://localhost:3000, which
is what the M0 integration spikes need.
Without make:
docker compose -f deploy/docker-compose.yml up --build -d --wait
./deploy/smoke.shTwo things the compose file does deliberately:
Named volume, not a bind mount. The container runs as uid 10001; a host directory would arrive owned by your user and every write would fail with
Permission denied.HIVE_SIGNING_KEYis fixed. Left unset, each start generates a new key and every download link minted before the restart stops working.
Connecting OpenWebUI
Both surfaces are added under Admin Settings → Integrations → +. Adding MCP servers is admin-only; regular users may only add OpenAPI servers.
MCP | OpenAPI | |
Type |
|
|
URL |
|
|
Auth |
|
|
Use http://host.docker.internal:8080 instead when OpenWebUI runs in a container and
HiveMCP does not.
Auth must be Session. HiveMCP has no shared secret and no service account: it
authenticates each caller by validating their own OpenWebUI session token, then reuses
that token against the Files API so generated documents belong to the person who asked
for them. Any other setting means no caller can be identified and every tool call is
refused.
Paste this into the connection's Headers field. These are context, not credentials — the identity comes from the validated token:
{
"X-Hive-Chat-Id": "{{CHAT_ID}}"
}X-Hive-Chat-Id becomes required as soon as HIVE_LLM_ENABLED=true: it is the only way
HiveMCP can find out which model you selected, since OpenWebUI offers no {{MODEL}}
token and __model__ reaches native Python tools only.
Both surfaces can be registered at once. MCP gives protocol compatibility with other clients; OpenAPI is the surface that can return Rich UI embeds, which the configuration GUI needs in M5.
Layout
hivemcp/
app.py FastAPI factory: health, download, both surfaces
config.py HIVE_* settings; refuses to start without OpenWebUI in prod
auth.py session-token validation, HMAC-signed download links
surfaces/
mcp_server.py MCP Streamable HTTP, stateless, mounted at /mcp
openapi_tools.py /tools/* with explicit operation ids
config_ui.py the settings card, rendered inline in the chat
download_ui.py the download card
theme_ui.py theme sync shared by both cards
skills_api.py GET /skills/{name}, the guide as plain Markdown
debug.py dev-only diagnostics under /_debug
core/
service.py shared logic: validation, render semaphore, delivery
models.py DeckSpec / DocSpec / SheetSpec / RenderOptions
delivery.py the three delivery modes
skills.py the bundled usage guide, loaded once at start-up
preferences.py interface locale, and how to read it
render/ pptx.py, docx.py, xlsx.py, markdown.py, theme.py
editing/ read.py and apply.py — patching uploaded files
templates/ admin-curated pool, inspection, upload validation
llm/ brief expansion through the user's selected model
files/owui_client.py OpenWebUI Files API
files/workdir.py artifact store, with TTL sweep
skills/hivemcp-usage/ SKILL.md, shipped inside the package on purpose
deploy/ Dockerfile, docker-compose.yml, smoke.sh, k8s/, PORTAINER.md
portainer-stack.yml Portainer stack, at the root because of build-context paths
docs/ OPENWEBUI_SETUP.md
tests/The renderers know nothing about FastAPI or MCP. Both surfaces are thin adapters over
core/, which keeps them from drifting apart and makes the interesting logic testable
without HTTP.
Design notes worth knowing
Specs are strict. Every model sets extra="forbid". A model that invents a field gets
a validation error it can correct on the next turn rather than a silently dropped value
and a wrong-looking document.
Untrusted text never becomes an Excel formula. Cell values starting with =, +, -
or @ are prefixed with ' unless the column is explicitly typed formula. Spreadsheet
content originates from model output and uploaded documents, so this is the formula
injection boundary.
Fonts are a name, not a file. OOXML stores only the font name; if the reader's machine lacks it, their viewer substitutes silently. Fonts outside the set that ships with Office produce a warning on the render result.
Page counts are estimates. Word paginates at open time using the installed fonts and
printer driver, so no library can know the real count. The result field is called
page_estimate for that reason.
Errors name their location. A failure on slide 7 says so, because the model needs to know where to fix the spec, not just what was wrong.
The cards read OpenWebUI's theme off the page, and follow it live. Nothing tells
them: postMessage carries no appearance message, window.args needs allowSameOrigin,
and OpenWebUI keeps the theme in the browser rather than in server-side settings. So the
cards look. OpenWebUI marks its own theme with class="dark" on <html>; each card
mirrors that class and watches it with a MutationObserver, which is what makes a theme
switch arrive without reloading the card.
prefers-color-scheme is the fallback, for when parent.document is out of reach —
cross-origin, or a sandbox without allow-same-origin. That is a normal configuration
rather than an error, so it is caught silently. It is second rather than first because it
only reflects OpenWebUI when OpenWebUI declares a color-scheme on the iframe element;
when it does not, the query falls through to the operating system, and a dark OpenWebUI on
a light desktop produced a white card in a dark chat.
The two must not contradict each other. Once the parent has been read the script records
the answer in data-parent-theme, and the media query is written to stand down whenever
that attribute is present — otherwise a parent correctly read as light would still be
painted dark by a dark OS.
Either way the card declares color-scheme: light dark on :root. Pinning a single
scheme replaces the transparent canvas with an opaque one, per CSS Color Adjust, turning
the card into a white rectangle inside a dark chat. There is no theme toggle and no
server-side guess: both existed to correct answers this no longer gets wrong.
Markdown maps what it can and warns about the rest. page_break becomes a thematic
break; a toc block is written out as links with GitHub-style anchors, because Markdown
has no field the reader's application fills in; images are embedded as data URIs, since
there is nowhere to put a companion file. Fonts, sizes, page size and orientation produce
a warning rather than being silently ignored — a dead option that changes nothing is worse
than one that says so. Escaping is deliberately narrow: only a marker that opens a line
is escaped, so prose containing an asterisk stays readable, while a pipe inside a table
cell is always escaped because it would end the cell.
A Markdown template is a plain .md file with {{placeholders}} and one {{content}}
marker. hive_inspect_template reports both, including when the marker is missing —
without it the body is appended, which is rarely what the author meant. Editing Markdown
is addressed by line rather than by paragraph: that is what a diff speaks, and a
paragraph index would have to be derived from blank-line grouping, which is a thing to get
wrong.
Language comes from the model, not the server. The interface locale is client-side too,
so hive_open_config takes a language argument and the tool description asks the model
to pass the language the conversation is in. English otherwise. Simplified and Traditional
Chinese are kept apart by script (zh-Hant, zh-TW/HK/MO → zh-TW) rather than collapsed
onto zh, because serving one to a reader of the other is a visible error.
The settings card travels inside the tool result. OpenWebUI embeds HTML returned by a
tool when the response carries Content-Disposition: inline, so no separate URL is
fetched and no signed link is needed. The card is fully sandboxed in return: nothing
inside can call back, so everything it needs is inlined at render time and the only way
out is postMessage.
Templates are an admin-curated pool. Administrators add and remove them; everyone
else lists, inspects and uses them. There are no private or per-group templates: a
template is a corporate design, and central curation is the point. That also removed a
question the earlier per-group layout could not answer, since group membership is not part
of the validated identity. The pool lives on its own volume (HIVE_TEMPLATES_DIR) because
it has the opposite lifecycle to rendered artifacts — rarely written, constantly read, and
worth keeping when the artifact volume is cleared.
A URL in a tool result is not a link. OpenWebUI renders tool results as JSON and only
the assistant's own message as markdown, so a download_url sitting in the result is
plain text. Every result therefore also carries download_markdown, a finished link the
model can paste, and hive_show_download turns it into a Rich UI card with a real button,
the file's size and any warnings that would otherwise be buried in the JSON. The button
opens in a new tab rather than using the download attribute: sandboxed iframes carry
allow-downloads, but triggering a download from inside one is unreliable and on iOS
impossible, so the server's Content-Disposition: attachment does the work instead.
Editing patches, it does not rebuild. hive_edit_document opens the uploaded file and
changes specific things in it. Parsing a document into a spec and re-rendering would be
simpler and would discard every piece of formatting the spec cannot express, which is most
of them. Two consequences worth knowing: operations are all-or-nothing, because a
half-applied edit hands back a file that looks finished and is not; and an operation that
matched nothing is reported rather than swallowed, because replace_text for a string
that does not occur succeeds trivially and changes nothing. The original file is never
modified — the edit comes back as a new upload.
Uploads are identified by their contents. An OpenWebUI file id carries no extension and
an uploaded filename is a claim, so the format is read from the zip's own directory
(ppt/presentation.xml, word/document.xml, xl/workbook.xml).
Templates are inspected, not guessed at. hive_inspect_template reports each layout
along with which value of the spec's layout enum it corresponds to, so a model does not
have to bridge the vocabulary gap itself. Uploaded templates are validated by reading
them, and rejected as archives first: an Office file is a zip, and an uploaded one is
untrusted input.
Configuration
See .env.example. The container holds no third-party credentials:
HIVE_OWUI_URL and HIVE_SIGNING_KEY are all it needs.
HIVE_OWUI_URL— required. Session tokens are validated here, so nothing can authenticate without it. It is a server-to-server URL: what works in your browser usually does not work from inside the container.HIVE_SIGNING_KEY— must be set explicitly when running more than one replica. Each pod otherwise generates its own key at start-up, and download links break whenever a request lands on a different pod than the one that signed the link.HIVE_TEMPLATES_DIR— the shared template pool. Defaults to a subdirectory ofHIVE_DATA_DIR; give it its own volume so the curated templates survive clearing the artifact volume. Seedeploy/k8s/00-storage.yaml.HIVE_ENVIRONMENT=dev— mounts the diagnostic endpoints under/_debug. They reflect request headers back to the caller, so setprodanywhere that is reachable by others.HIVE_DELIVERY_MODE—both(default),owuiorlink. Inowuinothing is written to the artifact volume and this server never needs to be reachable from a browser, soHIVE_PUBLIC_URLand the ingress become unnecessary. The cost is that a failed upload loses the render, because there is no second copy to fall back on.HIVE_OWUI_PUBLIC_URL— only used byHIVE_DELIVERY_MODE=owui: the address a browser reaches OpenWebUI at, as opposed toHIVE_OWUI_URL, which is the one this container uses and is usually a service name no laptop can resolve.
Adding a template
Attach the file in the chat as an administrator and say what it should be called:
Save this as a template called "Corporate Deck 2026"
HiveMCP validates it by opening it, stores it in the shared pool, and returns the same
report hive_inspect_template gives — layouts, styles and {{placeholders}} — so no
second call is needed. A Markdown template is a plain .md file instead: no layouts to
inspect, just placeholders and a {{content}} marker saying where the generated body
goes. Everyone can then use it by passing template_id in
RenderOptions. Non-administrators get a 403 that says so rather than a validation error
they would try to fix by retrying.
This server cannot be installed
Maintenance
Related MCP Connectors
Generate, edit, merge, translate and PDF-convert PowerPoint (.pptx) over MCP. 8 tools.
Document-to-Markdown MCP server — convert PDF, Office and HTML into LLM-ready Markdown.
An agent-first office suite Claude & ChatGPT read and write over one MCP URL.
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Related MCP Servers
- FlicenseAqualityCmaintenanceMCP server for Microsoft Office file operations. Read, write, and create Excel, Word, and PowerPoint files directly from your local filesystem.12
- FlicenseBqualityDmaintenanceMCP server for Office document automation, enabling AI assistants to create, read, and clone Word/Excel/PPT templates while preserving formatting.41
- AlicenseAqualityDmaintenanceMCP server for Word document (.docx) creation and manipulation — the production-grade document automation tool for AI agents.963MIT
- FlicenseAqualityCmaintenanceMCP server for rendering, parsing, converting, and editing Office documents (Excel, PowerPoint, Word) from a structured JSON spec, using LibreOffice WASM for conversion and preview.51
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/traxonLp/HiveMCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server