Galley MCP
Use Galley as a local MCP bridge to inspect a shared Typst project and submit reviewed changes without direct write access.
Ping the kernel for liveness and version.
Read the session’s target document.
List live project files with paths, sizes, and binary-file metadata.
Read any project file by exact path, with size limits and binary metadata fallback.
Query ranked, provenance-tagged excerpts across all live files.
Propose search/replace edits to the session file for human review or armed auto-accept.
Propose atomic multi-file create, edit, rename, delete, or binary changes for review.
Compile the project to get Typst diagnostics and page count without modifying the document.
Allows using a local Ollama instance as the AI provider for Galley's document editing agent.
Allows using OpenAI or any OpenAI-compatible endpoint as the AI provider for Galley's document editing agent.
Allows importing Overleaf projects into Galley via .zip archives.
Allows importing references from Zotero/BibTeX (.bib) libraries into Galley.
The workspace above is the bundled Annus Mirabilis demo: multiple Typst files, a real nine-page compile, source editing and the offline Demo agent. Open Files on the left rail to navigate the project; use Export for the finished document or ⌘K / Ctrl+K to find an action.
The offline Demo model proposes a sample section without an API key. In Ask mode, inspect the compiled proposal and choose Accept or Reject. The screenshot was taken before Accept; the capture also verified that accepting updated the document, compiled cleanly and survived a reload.
In Settings → Appearance, choose Studio or Press, then Light, Dark or System. Artwork and Plain control the desk background independently. These choices change the workspace; document styling stays in Typst and the Change style command.
A galley proof is the preliminary typeset version you check and correct before final layout — which is exactly this app's core loop: compile → inspect → correct → repeat.
Galley is, conceptually, "AI-enhanced ShareLaTeX, but Typst instead of LaTeX, open source, and not locked to one vendor's cloud or model." The differentiation is three bets a cloud-only, single-vendor product structurally cannot make:
Agentic, not autocomplete. Typst compiles in milliseconds and emits structured, machine-readable diagnostics. That makes a tight edit → compile → read errors → self-correct loop cheap. That loop is the product.
Local-first. The Typst compiler runs in the browser via WASM (
typst.ts). Your document and your compile stay on your machine. Server-side compile is an optional enhancement, never a dependency. (See the honest local-first definition — remote model endpoints still receive your document.)Bring-your-own-model. A provider abstraction runs the agent against any OpenAI-compatible endpoint, Anthropic, or a local Ollama. Model choice is config, never a fork.
Status
Galley works end-to-end in the browser today: a CodeMirror editor with a live
SVG preview (typst.ts compiling in a Web Worker) and the human-in-the-loop
agent loop — request → self-correct → diff → Accept/Reject → re-render.
Bring your own model (any OpenAI-compatible endpoint, Anthropic, or a local
Ollama) or use the built-in offline Demo agent. Real-time collaboration is
opt-in — one-click Share upgrades a project to Yjs CRDT co-editing with the
agent as a peer, presence, and per-author attribution (ADRs
0005–0012). Multi-file projects and templates, named versions
with visual compare, broad import/export (Markdown, LaTeX, Overleaf .zip,
Zotero/.bib and native source .tar in; PDF/PNG, source bundles and Git projections out), and opt-in MCP access behind an explicit
consent gate round it out. Self-host with Docker compose or Kubernetes; the
whole test suite (typecheck + unit + Playwright e2e) runs in Docker. See
the CHANGELOG for the full feature inventory and the
roadmap for what's next.
Review the document before sharing
The writing/agent pane has deterministic, offline review tools. Open Agent panel from the left rail if that pane is hidden.
Open | What you can review |
Before you send | Current preview SVG page count and optional portrait A4/Letter checks, plus source editorial markers. Results distinguish passed, failed and unavailable; stale previews do not pass. PDF export is compiled separately. |
Meaning changes to check in a proposal diff | Changed numbers, dates, units, citation keys and selected obligation/negation words alongside the normal diff. These are attention flags, not semantic or factual verification. |
Document workbench → Living numbers | Import project CSV/JSON, name a scalar and link its exact document passage. Changed source versions flag potentially stale prose; preview and Accept a compiled refresh. |
Document workbench → Evidence lens | Manually link a claim passage to a retained source version and excerpt, mark your review, and see changed or unavailable links. This records a review decision, not an automatic fact-check. |
Layout auditions on the project main document | Compile three local scratch margin variants, compare previews and page caps, then review and Accept one. Original wording/assets/text-size rules stay intact; minimum rendered font size is not verified. |
Document workbench → Reviewer response desk | Paste comments, link passages, track resolutions, review proposed revisions and draft replies; use Download response letter for Markdown export. |
Workbench revisions and layout auditions require a successful local scratch compile and explicit Accept. Accept rechecks current source/project input, including main entry and resolved asset bytes, before any live write. Missing or changed dependencies require a new preview. These tools do not certify PDF compliance, claim truth or minimum font size.
Workbench records live in the canonical project CRDT and retain their source
versions. Native source bundles and Git projections carry only the validated
known .galley/workbench.json record alongside existing export contracts;
device/secret metadata stays excluded. Native import preserves source paths,
contents and binary bytes through the review-and-Accept flow. See
document checks and layout auditions and the
workbench workflows and limits.
Default product & routes
Galley uses path-based routes (a tiny History-API router):
/— the Projects page, the landing surface (create / open / import projects)./p/<id>— a specific local-first project (auto-saved to the browser, reload-survivable)./library— the project dashboard./join/<room>— share-link entry (joins a collaboration room; carries?sync=/?role=)./settings— device-scoped settings.
Collaboration is an explicit Share action, never on by default. The only
editor-on-home hatch is ?seed=… (the seeded Einstein showcase, also the e2e entry).
Related MCP server: mcp-server-test
The core loop
User request
│
▼
Agent proposes edits (search/replace blocks)
│
▼
Apply to a SCRATCH copy ──► compile ──► read diagnostics
│ │
│ ◄── errors? revise (up to MAX_ITERS) ─┘
▼
Clean compile → present a reviewable diff
│
▼
User clicks Accept ──► apply to live document
or Reject ──► discard scratchAsk is the default: proposals wait for human Accept or Reject. Explicit per-project Auto settings for the in-app agent and separately authorized MCP grants use the established guarded acceptance paths, with checkpoints and Undo; MCP proposals additionally require authenticated provenance. See the acceptance decision and security model. Workbench revisions and layout auditions retain their explicit human review gate.
Bring your own agent (no API key)
A configured cloud model usually needs a provider API key and may charge per token; the offline Demo agent and suitable local models do not. You can also use an agent you already run: Galley ships a local MCP kernel that carries no model at all. No provider, no key, no inference. It is a bridge that exposes your project as tools to an agent you already run, so Claude Code or Codex drives Galley on the plan you already pay for.
# One-time: install the kernel's bin from your checkout
cd apps/mcp && pnpm link --global
# Share the project in the browser to mint a room id, then:
claude mcp add galley -- galley-mcp \
--sync ws://localhost:1234 --room <room-id> --file /main.typYour agent gets the live document, the file tree, query-relevant excerpts across
files, and real Typst diagnostics. What it does not get is write access:
propose_edit and propose_files publish a pending review card, and a human
clicks Accept. That gate is structural, not a preference.
Control mode goes further and lets the agent list your library and ask to open a project, behind a one-time pairing code, per-project read grants that default to zero, and a blocking confirmation for every open.
→ Use your own coding agent with Galley for the walkthrough, or the full kernel reference for every flag.
Monorepo layout
galley/
├─ apps/
│ ├─ web/ # React + Vite app: editor, preview, agent panel, diff UI
│ ├─ web-server/ # tiny Hono static server for the built SPA (self-host runtime)
│ ├─ sync/ # @galley/sync — optional y-websocket collaboration server
│ ├─ compile/ # optional server-side Typst compile service (Hono + @galley/compiler)
│ ├─ proxy/ # thin, optional, self-hostable model-API proxy (keys off the browser)
│ └─ mcp/ # inbound MCP local kernel: exposes the agent tools to external MCP clients
├─ packages/
│ ├─ compiler/ # typst.ts wrapper: check(), render(), export() — in a Worker
│ ├─ agent/ # agent loop, tools, provider abstraction
│ ├─ collab/ # Yjs CRDT doc, presence, and cross-peer author attribution
│ ├─ auth/ # generic OIDC (Auth Code + PKCE) auth core
│ ├─ persistence/ # ProjectStore / CrdtStore / VersionStore adapters
│ └─ shared/ # cross-package types: Diagnostic, EditBlock, AgentEvent, …
├─ docs/ # foundation docs (start here ↓)
├─ AGENTS.md # rules for AI agents working ON this repo
└─ CONTRIBUTING.mdcompiler and agent are framework-agnostic (no React) so they can be reused
server-side. The optional server pieces — apps/proxy (model proxy, keys off the
browser), apps/sync (collaboration relay), and apps/compile (server-side
compile) — are self-hostable and used only when configured. The sync relay can
optionally persist project state; the proxy is stateless. The default browser
single-user path needs none of them (see the ADRs).
Documentation
Read these in order:
Doc | Purpose |
Product principles, the three bets, non-goals, honest definitions | |
Package boundaries, data flow, the worker model, source-of-truth rules | |
What's built, what activates with config, future directions | |
Threat model, per-surface posture, deployment hardening checklist | |
Docker compose + Kubernetes packaging | |
Server topology, accounts/auth, Yjs collaboration, agent-as-peer (design rationale) | |
The agent state machine, tools, iteration/error/cancel handling, event stream | |
Search/replace contract, scratch isolation, diff, Accept/Reject conflicts | |
Current-output/source checks, meaning flags and compiled layout auditions | |
Living numbers, evidence links, reviewer responses and portable records | |
typst.ts init, worker protocol, fonts, diagnostics normalization, preview/export | |
Provider types, capability probing, CORS/key caveats, local vs remote privacy | |
Drive Galley from Claude Code or Codex over MCP, on your own plan instead of an API key | |
Architecture Decision Records (ADRs) |
Quick start
The fastest path — run it with Docker (no toolchain needed):
docker compose up --build # → http://localhost:8080The Demo (offline) model lets you try the full agent loop with no provider or API key configured; open Settings to point at an OpenAI-compatible endpoint, Anthropic, or a local Ollama.
For development, use Node ≥ 20 and pnpm 9.15.9, as declared in
package.json:
pnpm install --frozen-lockfile
pnpm dev # runs @galley/webThe canonical checks run in Docker from the current source:
scripts/check-ci # lint, security/dependency audit, typechecks, PostgreSQL/unit tests, web build, Chromium
scripts/check-ci unit # iteration: lint, typechecks and unit tests
scripts/check-ci cross # Firefox/WebKit smoke for UI/compiler changesThe wrapper builds fresh images and collects artifacts in .ci-results by
default. See security scanning for additional scanners.
Self-hosting
docker compose up --build starts web + proxy + sync, with their published
ports bound to loopback. Open localhost:8080. The browser
uses proxy/collaboration only when configured; server compile stays off.
For a web-only process, run docker compose up --build web. The web app compiles
Typst in the browser by default, so it is complete on its own. See the
self-host guide and Kubernetes README.
Enabling server-side compile (opt-in). The heavier compile service isn't run
by default, so Settings → Compile → Server reports "Not configured." To wire it
up, use the ready-to-run overlay:
docker compose -f docker-compose.yml -f docker-compose.compile.yml \
--profile compile up --buildIt pre-sets GALLEY_COMPILE_URL=http://127.0.0.1:3001/compile (the
browser-reachable endpoint) so Server/Auto "just work"; the default
docker compose up leaves compile disabled. Details in
docs/self-host.md.
License
Galley is licensed under AGPL-3.0-only (see LICENSE): use,
modify, and self-host freely; if you offer a modified Galley to others over
a network, you must publish your modified source under the same license. The
"Galley" name and logo are protected separately by the
trademark policy — forks must rename. Contributions require
the CLA (see CONTRIBUTING).
The Typst compiler Galley builds on is Apache-2.0 (permissive — building on
it is fine); keep the name distinct (don't name anything "Typst") and ship
Typst's NOTICE if you ever redistribute its binary. See
docs/vision.md#licensing--branding.
Available Tools
8 toolscompileCompile the document for diagnosticsA
Type-checks the shared project and returns its diagnostics (errors/warnings + page count); never modifies the document. Uses the configured loopback compile service when one is set (--compile-url); otherwise falls back to the paired browser's live preview compiler. Call this after propose_files/propose_edit to confirm the change set type-checks before asking the human to Accept.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden. It explicitly discloses safety ('never modifies the document'), the two possible compile backends (loopback service or browser live preview), and the output shape (errors/warnings + page count). This is strong disclosure beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: what it does, how it does it, and when to invoke it. Front-loaded with the core action and result, with the usage guidance placed last as a natural follow-up.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is complete for a zero-parameter, no-output-schema tool. It tells the agent the result format, the safety guarantee, the backend selection mechanism, and the precise workflow placement relative to siblings. No critical information is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline is 4. The description correctly avoids inventing parameter semantics and instead focuses on what the tool does and returns. Nothing is missing here.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Type-checks') with a clear resource ('the shared project') and states what it returns ('diagnostics (errors/warnings + page count)'). It also clarifies a key boundary ('never modifies the document'), distinguishing it from the propose_* sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says when to call the tool: 'Call this after propose_files/propose_edit to confirm the change set type-checks before asking the human to Accept.' This is clear context, though it does not spell out when not to use it or name direct alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
galley_pingPing the Galley MCP kernelB
Liveness check. Returns 'pong' with the kernel version.
| Name | Required | Description | Default |
|---|---|---|---|
| echo | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It does disclose the return payload ('pong' plus kernel version), which is the main behavioral fact, but it never states that the call is side-effect-free, whether it can fail/timeout, or how the optional echo parameter affects the response.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences with zero waste, and the core fact (what it does, what it returns) is front-loaded. Nothing could be removed without losing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a trivial no-output-schema liveness tool, the description covers purpose and return value adequately, but it omits any explanation of the one parameter and gives no indication of failure behavior. Acceptable but with a clear gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the description never mentions the single 'echo' parameter, leaving its purpose (likely echoing the input back alongside 'pong') entirely undocumented. With a non-zero parameter count, the baseline-4 zero-parameter rule does not apply, and the description adds nothing here.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific operation ('Liveness check') and what it returns ('pong' with the kernel version), which is unambiguous against siblings that are all file/edit/compile operations. It does not explicitly name a sibling or contrast itself, but the function is distinct enough that no agent would confuse it with read_file or compile.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Liveness check' strongly implies when to call it (verifying the kernel is reachable), but there is no explicit guidance on when not to use it, no prerequisite notes, and no mention of alternatives. Usage is inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_filesList the project's filesA
Lists the live (non-deleted) files of the shared project — exact path + size (UTF-8 bytes when sizeExact, else a cheap lower bound), deterministic order; duplicate-path conflicts are flagged per entry. BINARY files (images/PDFs) appear too, carrying a { kind:"binary", hash, mime } pointer descriptor with an exact sizeBytes — pass that path to read_file to get the pointer. Read-only context: edits still go through propose_edit and target only the session's file.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It discloses size semantics (UTF-8 bytes when sizeExact, else a cheap lower bound), deterministic ordering, duplicate-path conflict flags, binary file pointer descriptors with exact sizeBytes, and the read-only nature of the operation. This is exceptionally transparent for a tool with no annotation support.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences pack in the core action, output details, binary-file handling, and read-only context without redundancy. The most important information is front-loaded, and every clause adds value. The use of em-dashes and semicolons keeps related ideas together without bloating the text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a parameterless tool with no output schema, the description fully explains what the agent will receive: paths, sizes, duplicate flags, and binary pointers. It also explains how to proceed with binary files (pass to read_file) and clarifies the mutation boundary (propose_edit). Nothing needed to invoke or interpret the tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline is 4. The description does not need to explain parameters; it instead focuses on output semantics. It mentions 'sizeExact' as a field, not a parameter, which is appropriate. No parameter-related gap exists.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Lists the live (non-deleted) files of the shared project.' It then details exactly what is returned (path, size, deterministic order, duplicate-path flags) and distinguishes itself from read_file by explaining how binary file pointers are consumed by that sibling. This leaves no ambiguity about what the tool does.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context: it is a read-only listing operation, and edits are explicitly routed to propose_edit. It also tells the agent to pass binary file paths to read_file for the pointer. It does not explicitly compare against project_context or galley_ping, but the read-only/edit exclusion and the read_file handoff give enough practical guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
project_contextQuery-relevant project contextA
Returns the most RELEVANT excerpts across ALL the project's live files for a query (lexical ranking over structural chunks), each with provenance: file path, 1-based line range, and Typst heading path. Budget-limited and read-only: files with duplicate-path conflicts or over the per-file cap are skipped (listed in skipped with a reason), and selectionTruncated reports when relevant chunks did not fit the budget. Use read_file for a complete file.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | What you are looking for — ranks chunks across the whole project. | |
| budget | No | Response budget in characters of excerpt text (default 6144). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so thoroughly: it declares read-only status, the budget-limited behavior, that duplicate-path conflicts and over-cap files are skipped and reported in `skipped` with reasons, and that `selectionTruncated` signals dropped chunks. This is unusually rich disclosure of what the caller will and won't get back.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and provenance contract, then the budget/skip caveats; nearly every clause carries information. It is a dense single paragraph with nested parentheticals, which is slightly heavy for a two-parameter tool but not wasteful.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, yet the description names the returned fields (file path, 1-based line range, heading path) and the two status signals (`skipped`, `selectionTruncated`) an agent needs to interpret a partial result. Nothing essential for correct invocation or result interpretation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema lacks: it explains that budget caps excerpt text and connects it to the `selectionTruncated` signal, and clarifies that `query` ranks across the whole project rather than a single file. That is genuine added semantic value beyond the field descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the most RELEVANT excerpts across ALL the project's live files for a query') plus the mechanism (lexical ranking over structural chunks) and scope (whole project, live files). An agent can immediately distinguish it from full-file readers like read_file.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes to the sibling tool: 'Use read_file for a complete file,' giving a clear alternative with the condition that selects it. It does not spell out the inverse case (when not to use this tool at all, e.g., when you already know the file path), so it falls just short of a full when/when-not/alternatives statement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_editPropose an edit (published for review)A
Applies search/replace edits to a scratch copy of the session's file and publishes the result as a proposal. It never edits the document directly: the change is published for review — if this project has agent auto-accept armed it may apply automatically (the response says status "applied"), otherwise it awaits a human Accept in Galley. Inspect the returned status to know what happened. Each search must match the current text exactly once; failures come back as structured data (status: "edit_failed") — refine and retry.
| Name | Required | Description | Default |
|---|---|---|---|
| edits | Yes | Search/replace blocks, applied in order against the running text. | |
| request | Yes | A short human-readable title for the change (shown in the review card). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and does so well: it discloses the scratch-copy/publish model, that the document is never edited directly, the auto-accept possibility, the two possible status values, and the exact-match-once constraint with structured failure feedback. This is genuinely useful behavioral context an agent could not infer otherwise.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then behavior, then failure handling in a logical sequence with no filler. It is somewhat dense with parentheticals and quoted status values, but every sentence carries load.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter mutation tool with no annotations and no output schema, the description covers the mutation model, permissions/auto-accept nuance, expected return status values, and error recovery. Nothing critical for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description goes beyond the schema by giving the `search` parameter operational meaning (must match exactly once, failures come back as status 'edit_failed' and should be refined and retried) and by noting the returned `status` must be inspected, which adds value over the static field docs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('applies search/replace edits to a scratch copy of the session's file and publishes the result as a proposal') and immediately distinguishes it from a direct-edit tool via 'It never edits the document directly.' An agent can tell what it does and that it is not the same as propose_files or read_document.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explains the two possible outcomes and when each occurs: auto-accept armed means status 'applied', otherwise a human Accept in Galley is required, and failures return status 'edit_failed' with a refine-and-retry instruction. It does not explicitly compare against sibling tools such as propose_files, but the usage context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
propose_filesPropose a multi-file change (published for review)A
Proposes a change set across MULTIPLE files as ONE atomic proposal — create new files, edit existing ones, rename/move them (rename also moves into a folder), or delete them (e.g. add chapters/intro.typ and #include it from main.typ). It never changes the project directly: the set is published for review — if this project has agent auto-accept armed it may apply automatically (the response says status "applied"), otherwise it awaits a human Accept in Galley. Inspect the returned status to know what happened; the whole set lands all-or-nothing. For edit ops each search must match that file's current text exactly once; failures come back as structured data — refine and retry. Delete is a recoverable soft-delete. Paths must be safe in-tree project paths (leading slash, no traversal, not under /.galley). For binary files you may either inline the bytes (create-binary, base64) or, when the source already lives on the machine running the Galley kernel, reference it by ABSOLUTE local path (create-binary-path) so no base64 transfer is needed — both publish the same content-addressed pointer. A single proposal is capped at 32 ops (with aggregate size limits); to change more files, split the work across multiple sequential propose_files calls — each is reviewed and lands independently (there is no import-group transaction spanning proposals).
| Name | Required | Description | Default |
|---|---|---|---|
| ops | Yes | The file operations — applied all-or-nothing on Accept. | |
| request | Yes | A short human-readable title for the whole change set (shown in the review card). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, and the description carries the full behavioral burden: it discloses that nothing changes directly, that auto-accept may apply automatically (status 'applied'), that the set is all-or-nothing, that edit search must match exactly once, that delete is a recoverable soft-delete, path safety rules, and the 32-op cap with no cross-proposal transaction. This is exactly the mutation semantics an agent needs and cannot get from schema alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the atomic multi-file purpose and lifecycle before operational constraints, and nearly every sentence carries new information. It is a single dense paragraph, though, and would scan better split into purpose / lifecycle / op-rules / limits sections.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description compensates by explaining the return signal ('inspect the returned `status`') and how failures surface ('failures come back as structured data — refine and retry'). Combined with the lifecycle, path-safety, and size-limit coverage, nothing an agent needs to invoke this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description earns above that by adding constraint semantics the schema doesn't state, notably the 32-op aggregate cap, the exact-match-once rule for `search`, and the distinction that create-binary-path reads a source file locally with no base64 transfer. It doesn't add per-parameter syntax beyond that, so not a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource with scope: 'Proposes a change set across MULTIPLE files as ONE atomic proposal', then enumerates the op kinds (create/edit/rename/delete/binary). The 'MULTIPLE' emphasis implicitly distinguishes it from the single-file sibling propose_edit, so an agent can route without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear when-to-use context (multi-file change sets), an explicit decision rule for binaries (inline base64 vs create-binary-path by absolute local path), and a concrete fallback for size limits ('split the work across multiple sequential propose_files calls'). It never names propose_edit as the single-file alternative, so routing to that sibling is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_documentRead the Galley documentA
Returns the current text of the session's target file from the live shared project.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. 'Returns' supports a read-only interpretation, and 'live shared project' hints at freshness, but the description does not explicitly state that the operation is non-mutating or describe failure behavior or prerequisites. It is adequate but minimal.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with no filler. Every word contributes to the meaning, and the structure is well suited to a zero-parameter read operation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given zero parameters and no output schema, the description adequately explains what the agent will receive: the current text of the session's target file. It does leave the concept of 'session's target file' implicit and does not contrast with read_file, but for a simple read tool this is largely complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters and the schema is empty, so there is no parameter semantics burden on the description. The description usefully clarifies that the implicit target is the session's target file, which is enough given the absence of parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Returns') and clearly identifies the resource (current text of the session's target file) and source (live shared project). It is clear in purpose, though it does not explicitly differentiate itself from the sibling read_file.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus alternatives like read_file, nor any mention of prerequisites, exclusions, or context that would help an agent decide between tools. The description only states what the tool does.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_fileRead a project file by pathA
Returns the live replicated text of any project file by EXACT path (as listed by list_files — no normalization, no globbing). Read-only. Files over 2097152 UTF-8 bytes are refused with an error, never truncated. A BINARY file (image/PDF) returns structured metadata { kind: "binary", path, hash, size, mime } instead of text — its bytes are not available through this tool.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Exact file path as returned by list_files, e.g. /notes.typ. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses read-only behavior, a hard 2 MiB ceiling that errors rather than truncates, and the exact shape of the binary result ({ kind, path, hash, size, mime }). It omits what happens on a nonexistent or unreadable path and what the text response's structure is, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with the primary behavior, then the refusal limit, then the binary exception. Every sentence carries a distinct operational fact and none repeats what the schema already says.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter, no-annotation, no-output-schema tool, the description covers the return payload in both text and binary cases plus the size limit, which is the information an agent cannot get elsewhere. Error semantics for missing paths and any permission requirements remain unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single parameter is already documented, so the baseline is 3; the description still adds real meaning by forbidding normalization and globbing, which the schema's 'exact file path' phrasing does not spell out. That constraint changes how an agent constructs the argument, so it earns above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the live replicated text of any project file') plus the exact-path constraint, which pins down scope precisely. Referencing list_files as the source of valid paths separates it from that sibling and from a generic document reader, so an agent can pick it out without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the path requirement ('as listed by list_files'), which tells the agent to enumerate first, but there is no explicit when-to-use/when-not guidance against read_document, which is the closest sibling. The agent must infer the boundary between raw file read and document read.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v2026.10.2- Changed
galley_ping1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
- Changed
project_context1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
- Changed
propose_edit2 fields changed- removed
Input schema / additionalPropertiesRemoved value: -false - removed
Input schema / properties / edits / items / additionalPropertiesRemoved value: -false
- Changed
propose_files3 fields changed- removed
Input schema / additionalPropertiesRemoved value: -false - removed
Input schema / properties / ops / items / anyOfRemoved value: -[ - { - "additionalProperties": false, - "properties": { - "kind": { - "const": "create", - "type": "string" - }, - "path": { - "description": "Canonical path of the NEW file, e.g. /chapters/intro.typ.", - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "text": { - "description": "Full text of the new file.", - "maxLength": 2097152, - "type": "string" - } - }, - "required": [ - "kind", - "path", - "text" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "edits": { - "description": "Search/replace blocks, applied in order against this file's text.", - "items": { - "additionalProperties": false, - "properties": { - "replace": { - "description": "Replacement text.", - "maxLength": 65536, - "type": "string" - }, - "search": { - "description": "Exact text to find in this file — must match exactly once.", - "maxLength": 65536, - "type": "string" - } - }, - "required": [ - "search", - "replace" - ], - "type": "object" - }, - "maxItems": 64, - "minItems": 1, - "type": "array" - }, - "kind": { - "const": "edit", - "type": "string" - }, - "path": { - "description": "Exact path of the EXISTING file to edit (as listed by list_files).", - "maxLength": 1024, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "path", - "edits" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "kind": { - "const": "rename", - "type": "string" - }, - "newPath": { - "description": "Destination path — also moves into a folder (e.g. /draft.typ → /chapters/intro.typ). Must be a free, safe in-tree path. References (#include/#image) are NOT auto-rewritten — bundle the corrective `edit` ops in the same proposal.", - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "path": { - "description": "Exact CURRENT path of the EXISTING file to move (as listed by list_files).", - "maxLength": 1024, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "path", - "newPath" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "kind": { - "const": "delete", - "type": "string" - }, - "path": { - "description": "Exact path of the EXISTING file to remove (recoverable soft-delete on Accept).", - "maxLength": 1024, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "path" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "bytes": { - "description": "The file's bytes, base64-encoded (standard base64; decoded size is capped).", - "maxLength": 89478490, - "minLength": 1, - "type": "string" - }, - "kind": { - "const": "create-binary", - "type": "string" - }, - "mime": { - "description": "Optional media type; inferred from the bytes/extension when omitted.", - "maxLength": 255, - "minLength": 1, - "type": "string" - }, - "path": { - "description": "Canonical path of the NEW binary file, e.g. /figures/logo.png.", - "maxLength": 1024, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "path", - "bytes" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "kind": { - "const": "create-binary-path", - "type": "string" - }, - "mime": { - "description": "Optional media type; inferred from the file's bytes/extension when omitted.", - "maxLength": 255, - "minLength": 1, - "type": "string" - }, - "path": { - "description": "Canonical IN-TREE path of the NEW binary file, e.g. /figures/logo.png.", - "maxLength": 1024, - "minLength": 1, - "type": "string" - }, - "srcPath": { - "description": "ABSOLUTE path of the source file on the machine running the Galley kernel — the kernel reads its bytes locally (no base64 transfer). Decoded size is capped at the blob limit.", - "maxLength": 1024, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "path", - "srcPath" - ], - "type": "object" - } -] - added
Input schema / properties / ops / items / oneOfAdded value: +[ + { + "properties": { + "kind": { + "const": "create", + "type": "string" + }, + "path": { + "description": "Canonical path of the NEW file, e.g. /chapters/intro.typ.", + "maxLength": 1024, + "minLength": 1, + "type": "string" + }, + "text": { + "description": "Full text of the new file.", + "maxLength": 2097152, + "type": "string" + } + }, + "required": [ + "kind", + "path", + "text" + ], + "type": "object" + }, + { + "properties": { + "edits": { + "description": "Search/replace blocks, applied in order against this file's text.", + "items": { + "properties": { + "replace": { + "description": "Replacement text.", + "maxLength": 65536, + "type": "string" + }, + "search": { + "description": "Exact text to find in this file — must match exactly once.", + "maxLength": 65536, + "type": "string" + } + }, + "required": [ + "search", + "replace" + ], + "type": "object" + }, + "maxItems": 64, + "minItems": 1, + "type": "array" + }, + "kind": { + "const": "edit", + "type": "string" + }, + "path": { + "description": "Exact path of the EXISTING file to edit (as listed by list_files).", + "maxLength": 1024, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "path", + "edits" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "rename", + "type": "string" + }, + "newPath": { + "description": "Destination path — also moves into a folder (e.g. /draft.typ → /chapters/intro.typ). Must be a free, safe in-tree path. References (#include/#image) are NOT auto-rewritten — bundle the corrective `edit` ops in the same proposal.", + "maxLength": 1024, + "minLength": 1, + "type": "string" + }, + "path": { + "description": "Exact CURRENT path of the EXISTING file to move (as listed by list_files).", + "maxLength": 1024, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "path", + "newPath" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "delete", + "type": "string" + }, + "path": { + "description": "Exact path of the EXISTING file to remove (recoverable soft-delete on Accept).", + "maxLength": 1024, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "path" + ], + "type": "object" + }, + { + "properties": { + "bytes": { + "description": "The file's bytes, base64-encoded (standard base64; decoded size is capped).", + "maxLength": 89478490, + "minLength": 1, + "type": "string" + }, + "kind": { + "const": "create-binary", + "type": "string" + }, + "mime": { + "description": "Optional media type; inferred from the bytes/extension when omitted.", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "path": { + "description": "Canonical path of the NEW binary file, e.g. /figures/logo.png.", + "maxLength": 1024, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "path", + "bytes" + ], + "type": "object" + }, + { + "properties": { + "kind": { + "const": "create-binary-path", + "type": "string" + }, + "mime": { + "description": "Optional media type; inferred from the file's bytes/extension when omitted.", + "maxLength": 255, + "minLength": 1, + "type": "string" + }, + "path": { + "description": "Canonical IN-TREE path of the NEW binary file, e.g. /figures/logo.png.", + "maxLength": 1024, + "minLength": 1, + "type": "string" + }, + "srcPath": { + "description": "ABSOLUTE path of the source file on the machine running the Galley kernel — the kernel reads its bytes locally (no base64 transfer). Decoded size is capped at the blob limit.", + "maxLength": 1024, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "kind", + "path", + "srcPath" + ], + "type": "object" + } +]
- Changed
read_file1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
8 tool updates
v0.1.0- First observed
compile - First observed
galley_ping - First observed
list_files - First observed
project_context - First observed
propose_edit - First observed
propose_files - First observed
read_document - First observed
read_file
TDQS
Scored across 8 tools
Most tools are clearly distinct: list_files enumerates, project_context retrieves ranked excerpts, compile type-checks, and propose_edit/propose_files separate single-file from multi-file proposals. The main overlap is read_file versus read_document, since both read file text and read_document is effectively a shortcut for the session's target file; propose_edit versus propose_files also has a boundary agents could misjudge, though the descriptions clarify it.
Most names follow a clear verb_noun pattern: read_file, list_files, read_document, propose_edit, propose_files. The deviations are project_context (noun phrase), compile (bare verb), and galley_ping (server-prefixed noun), but they remain readable and do not obscure intent.
Eight tools is a well-scoped surface for a collaborative document-editing server. Each tool covers a distinct stage: liveness, listing, reading, contextual retrieval, single-file proposals, multi-file proposals, and compilation.
The lifecycle is largely covered: list files, read text, gather context, propose edits across one or many files, and type-check before human acceptance. Minor gaps include no explicit tool for inspecting proposal history/status beyond the immediate response, and no direct diff or review workflow, but these appear intentionally delegated to Galley's human review UI.
Maintenance
Related MCP Connectors
MCP server for Product Management
Related MCP Servers
- -
- -
- AGPL 3.0
- MIT