Skip to main content
Glama

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:

  1. 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.

  2. 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.)

  3. 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 scratch

Ask 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.typ

Your 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.md

compiler 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

docs/vision.md

Product principles, the three bets, non-goals, honest definitions

docs/architecture.md

Package boundaries, data flow, the worker model, source-of-truth rules

docs/roadmap.md

What's built, what activates with config, future directions

docs/security-model.md

Threat model, per-surface posture, deployment hardening checklist

docs/self-host.md

Docker compose + Kubernetes packaging

docs/server-and-collaboration.md

Server topology, accounts/auth, Yjs collaboration, agent-as-peer (design rationale)

docs/agent-loop.md

The agent state machine, tools, iteration/error/cancel handling, event stream

docs/editing-and-diff.md

Search/replace contract, scratch isolation, diff, Accept/Reject conflicts

docs/document-checks.md

Current-output/source checks, meaning flags and compiled layout auditions

docs/document-workbench.md

Living numbers, evidence links, reviewer responses and portable records

docs/compiler.md

typst.ts init, worker protocol, fonts, diagnostics normalization, preview/export

docs/providers.md

Provider types, capability probing, CORS/key caveats, local vs remote privacy

docs/mcp.md

Drive Galley from Claude Code or Codex over MCP, on your own plan instead of an API key

docs/decisions/

Architecture Decision Records (ADRs)

Quick start

The fastest path — run it with Docker (no toolchain needed):

docker compose up --build    # → http://localhost:8080

The 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/web

The 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 changes

The 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 --build

It 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 tools
compileCompile 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.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

NameRequiredDescriptionDefault
echoNo

TDQS

B3.3/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

NameRequiredDescriptionDefault
queryYesWhat you are looking for — ranks chunks across the whole project.
budgetNoResponse budget in characters of excerpt text (default 6144).

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

NameRequiredDescriptionDefault
editsYesSearch/replace blocks, applied in order against the running text.
requestYesA short human-readable title for the change (shown in the review card).

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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).

NameRequiredDescriptionDefault
opsYesThe file operations — applied all-or-nothing on Accept.
requestYesA short human-readable title for the whole change set (shown in the review card).

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

NameRequiredDescriptionDefault
pathYesExact file path as returned by list_files, e.g. /notes.typ.

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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.

  1. 5 tool updatesv2026.10.2
    • Changedgalley_ping1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedproject_context1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedpropose_edit2 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • removedInput schema / properties / edits / items / additionalProperties
        Removed value: -false
    • Changedpropose_files3 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • removedInput schema / properties / ops / items / anyOf
        Removed 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"
        -  }
        -]
      • addedInput schema / properties / ops / items / oneOf
        Added 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"
        +  }
        +]
    • Changedread_file1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
  2. 8 tool updatesv0.1.0
    • First observedcompile
    • First observedgalley_ping
    • First observedlist_files
    • First observedproject_context
    • First observedpropose_edit
    • First observedpropose_files
    • First observedread_document
    • First observedread_file

TDQS

A4/5.0

Scored across 8 tools

Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivityActive
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers