Skip to main content
Glama

glm-mcp

npm ci license

An MCP server that exposes Z.ai's GLM models — GLM-5.3 and siblings — as tools inside any MCP client. Verified in Claude Code / Claude Desktop, Codex and Antigravity.

The original reason was Claude Desktop, which pins its own model provider: when it spawns its embedded Claude Code it forces ANTHROPIC_BASE_URL to Anthropic's endpoint and strips ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN from the child environment. So GLM cannot drive a desktop session. This server takes the other route — GLM becomes a tool the agent calls mid-conversation — and that route happens to work everywhere, because MCP is a standard and nothing in here is Claude-specific.

Useful for:

  • A real second opinion. An independent frontier model, not the same model asked twice.

  • Very large context. GLM-5.3 has a million-token window, so you can hand it far more source material than fits in a normal session.

  • Cheap bulk work. Route grunt work to glm-4.7 and keep the expensive model for reasoning.

Install

Register it once, user-scoped, and it is available in every project on the machine — no per-project setup.

Claude Code / Claude Desktop

claude mcp add --scope user glm -- npx -y @nocompromiseai/glm-mcp@0.7.0

Codex

codex mcp add glm -- npx -y @nocompromiseai/glm-mcp@0.7.0

Antigravity

agy mcp add glm -- npx -y @nocompromiseai/glm-mcp@0.7.0

One caveat that decides whether file context works at all, per host. This server confines file reads to the directory it was started in (see Path confinement), so where the host launches it matters:

host

launches the server in

so

Claude Code

the project directory

works as-is

Codex

the session directory

works as-is

Antigravity

wherever agy itself was invoked — and --add-dir does not change this

either launch agy from the project, or set GLM_MCP_ROOTS and pass cwd

For Antigravity, set the roots at registration:

agy mcp add -e GLM_MCP_ROOTS=/abs/path/one:/abs/path/two glm -- npx -y @nocompromiseai/glm-mcp@0.7.0

Note that GLM_MCP_ROOTS bounds where a caller may point cwd; it does not exempt the launch directory, which must also be inside a root. When it is not, the call is refused with a message naming the offending cwd — loudly, rather than quietly matching no files.

Sharing one server between several agents has a cost the roots do not show: file reads are synchronous, so a call that reads a tree delays every other call on that server until it finishes — measured, a trivial call waited over 200× its own latency beside a 79 MB read. See Limits for that, and for what GLM_MCP_GLOB_TIMEOUT_MS does and does not bound.

The version is pinned on purpose: a bare npx resolves latest at run time, while the package itself ships the exact dependency graph it was tested with — pinning is what makes the two the same graph. Bump the pin when you upgrade.

One exception, and it matters if you are working on this package rather than with it. npx prefers a local package whose name and version satisfy the request, so inside a checkout of this repository whose package.json version equals the pin, npx runs your working tree, not the published tarball. That is often what you want while developing — but an unbuilt checkout has no dist/ to run and the server dies at startup with sh: glm-mcp: command not found. Either build it (npm install runs the build), or register the absolute path instead, which makes the substitution explicit:

claude mcp add --scope user glm -- node /absolute/path/to/glm-mcp/dist/index.js

Or from a local checkout:

npm install && npm run build
claude mcp add --scope user glm -- node /absolute/path/to/glm-mcp/dist/index.js

Restart the host for it to be picked up. Verify with claude mcp list, codex mcp list or agy mcp list.

Requires Node 20 or newer, and a z.ai API key with credit or a Coding Plan.

Related MCP server: CCGLM MCP Server

Credentials

The server never hardcodes a key. It resolves one from, in order:

  1. ZAI_API_KEY

  2. ~/.config/zai/api-key

  3. the api.z.ai key ZCode stores in ~/.zcode/v2/config.jsononly when GLM_MCP_ALLOW_ZCODE_KEY=1 is set

Step 3 is opt-in on purpose. It reads a credential belonging to a different application, and that should be a decision you make rather than behaviour you discover. Everything runs on the machine doing the installing: your key, your z.ai account, your billing.

The key and the endpoint are resolved on every call, not once at startup, so rotating the key on disk takes effect on the next request and needs no restart. Nothing is rebuilt when nothing changed — an unchanged key and endpoint keep the same connection — and if a re-read fails while a working credential is already in use, the call is served from it rather than failing, with the reason on stderr. A credential that resolved once is evidence one exists, and a transient unreadable config should not take down a running server.

Note that the ZCode Start Plan token is not usable here — that endpoint is captcha-locked to the ZCode app and rejects outside clients with 3007. You need an api.z.ai key with a Coding Plan or credit on it.

Usage

Once registered, ask Claude to use it. In practice you say something like "use glm_ask to review src/auth for race conditions" — but the underlying call looks like this:

{
  "prompt": "Does the refresh logic have a race condition? Point at the lines.",
  "files": ["src/auth/**/*.ts"],
  "reasoning": "high"
}

The reply carries a footer with the model, token usage and how much it reasoned:

The refresh path in session.ts:88 reads `expiresAt` before taking the lock ...

[glm-5.3 · in 4210 / out 380 tok · reasoned 2170 chars]

Two things it is genuinely good at:

  • A second opinion that disagrees. Ask Claude and GLM the same question and compare. Two models disagreeing is real signal; the same model asked twice mostly is not.

  • More source than fits. With a 1M-token window you can hand it src/**/*.ts wholesale instead of curating a handful of files.

Tools

glm_ask

arg

type

default

notes

prompt

string

required

messages

{role, content}[]

the conversation so far, as prior turns this call continues — see below

files

string[]

files to include as context, each numbered cat -n style: literal paths (optionally with an inclusive line range, src/auth/session.ts:40-120) and/or globs (src/**/*.ts, *.md, {lib,src}/*.ts)

include

string

send only the expanded files whose content contains this literal text ("refreshToken"). Matched against what is in each file, never its path, and applied before the character budget, so a large file about to be dropped cannot crowd out a small one that matches. A literal substring, not a regex. If nothing matches, the call is refused rather than answered without the material

images

string[]

image paths to attach (PNG, JPEG, GIF, WebP), read under the same confinement and the same size limit as files. Requires a model that accepts image input; with any other model the call is refused, because an answer that silently ignored the images would read exactly like one that had looked at them

schema

object

a JSON Schema the answer must take. Sent as a forced tool, not asked for in the prompt: asking is unenforced, and a caller cannot tell a formatting drift from a real answer without parsing — the parsing this removes. The response is the structured value. If the model answers in prose instead the call fails, and its text is withheld deliberately, because a caller that asked for a shape cannot use text it would have to parse

cwd

string

server cwd

what relative files resolve against

model

string

glm-5.3

any id from glm_models

reasoning

none|low|high|max

low

higher is slower

system

string

optional system prompt

max_tokens

number

the model's own default (65,536 for GLM-5.3)

hard ceiling on output; see Reasoning

A long call reports that it is alive. GLM-5.3 at reasoning: "max" over a near-full context window can run for many minutes, and silence for that long is indistinguishable from a hang — so if your client passes an MCP progress token, the server sends a progress notification every GLM_MCP_PROGRESS_MS (five seconds by default) while the call is in flight. No token, no notifications. The answer itself is unchanged by any of this.

Pushing back on an answer

Every call used to be independent, and the natural second-opinion flow is disagreement — "you flagged session.ts:88, but the caller holds the lock." Re-sending the whole file context and hoping the consultant re-derives its own prior reasoning is what messages removes: pass the conversation so far, and prompt as the push-back.

{
  "prompt": "You flagged session.ts:88 — but the caller holds the lock across that whole block. Re-read it.",
  "files": ["src/auth/**/*.ts"],
  "reasoning": "high",
  "messages": [
    { "role": "user", "content": "Does the refresh logic have a race condition? Point at the lines." },
    { "role": "assistant", "content": "The refresh path in session.ts:88 reads expiresAt before taking the lock ..." }
  ]
}

The caller owns the history — there is no server-side session, so nothing is lost when the server restarts underneath you. Append each turn as the conversation grows and re-send the array; prompt stays required and is always sent as the final user turn, so never repeat it inside messages.

  • File context rides the first turn. With files and messages together, the context is attached to the thread's first turn and never repeated on the newest. A thread is exactly the shape prefix caching rewards, and the prefix stays cacheable only while the context stays at the front — measured on this account, a stable prefix with a varying tail read 32,429 input tokens down to 45. Move the context to the latest turn and every follow-up re-prefills it, which is the cost threads exist to remove.

  • The thread spends the same budget as the files. Prior turns count against the same character budget (GLM_MCP_MAX_FILE_CHARS, or the per-model derivation), so a long thread leaves less room for file context. The history itself is never cut — it is your own record of what was said — the files give way, and the truncation note says so: truncated at 300 total chars (...; the thread history spent 1200 of it).

  • Roles are checked here, not by z.ai. Each turn's role must be user or assistant; anything else is refused before anything is sent, naming the value you sent, rather than discovered as a 422 after the round trip. Within that, no ordering is imposed — z.ai accepts an assistant turn first, consecutive turns of one role and a trailing assistant turn — so replay a real transcript as it happened.

glm_review

Review a change and get a verdict back. This is the review half of the delegate → review loop, which used to live only in a bash script — putting it here is what lets Claude, Codex and Antigravity all review to the same standard instead of each hand-rolling a prompt.

arg

type

default

notes

diff

string

the unified diff to review, as your tooling produced it

files

string[]

optional files as extra context, resolved exactly as glm_ask resolves them

spec

string

what the change was meant to do; reaches the reviewer verbatim

cwd

string

server cwd

what relative files resolve against

model

string

glm-5.3

any id from glm_models

reasoning

none|low|high|max

high

higher than glm_ask's default, on purpose

max_tokens

number

the model's own default

hard ceiling on output

At least one of diff or files is required. Called with neither, the call is refused rather than answered — reviewing nothing and reporting a pass is the failure this tool exists to prevent.

The reply always ends with VERDICT: PASS or VERDICT: CHANGES_REQUIRED, on its own final line, spelled exactly that way. That is the vocabulary bin/glm-review greps for, so a shell pipeline can consume the result; the two parsers are checked against each other by npm run verify:review so they cannot drift apart.

A bare verdict comes back as an error, never as a clean review. A reply carrying less than GLM_REVIEW_MIN_SUBSTANCE characters of analysis beside its verdict is refused. This is the part most worth knowing about: a reviewer that returns "looks good" is worse than no reviewer, because it converts an unexamined change into one with a clean bill of health. The floor measures the model's reply, never the request — a floor computed from the prompt or the diff is satisfied by exactly the long requests that most need checking. A reply the output cap severed is refused too, whatever verdict it had already written.

Pass spec where you can. Review against intent is what catches silent scope-narrowing — an agent quietly implementing less than was asked while every test still passes — and no amount of reading the diff finds that on its own.

Reviews get the same heartbeat glm_ask gets: a progress notification every GLM_MCP_PROGRESS_MS while the reviewer works, sent only to a client that passed a progress token. A review is the same minutes-long model call, and its caller stares at the same black box without one.

This server never runs git. The diff comes from the caller. The trust boundary here is built around reading files (see Path confinement), and executing a subprocess in a caller-influenced directory is a different and much larger surface.

Use a different model than the one that wrote the code where you can. A model re-reading its own work reliably under-reports.

glm_models

Lists the model ids available on the configured account, each with a one-line role. The request path is text-only — no image is ever attached — so picking a model that accepts images (glm-4.6v and siblings, and glm-5.3-flash, which is natively multimodal though its name says nothing of it) forgoes the modality it was selected for; each such id's role says so. An id this server's model table does not know is listed bare: it stays z.ai's to describe, exactly as it stays z.ai's to size.

A verdict instead of paragraphs

When you want { finding, severity, lines } rather than prose, pass a schema and get the value:

glm-mcp ask -f 'src/**/*.ts' --schema '{"type":"object","properties":{"finding":{"type":"string"},"severity":{"type":"string","enum":["P1","P2"]}},"required":["finding","severity"]}' \
  "the worst thing in here"

stdout is the JSON value, so it pipes into jq and composes. glm_ask takes the same schema parameter over MCP.

It is sent as a forced tool, never as a request in the prompt. "Reply as JSON like {…}" is unenforced: the model can preface it, fence it, apologise first, or drift on the twentieth call after nineteen good ones — and you cannot tell a formatting drift from a real answer without parsing, which is the parsing this exists to remove.

If the model answers in prose anyway, the call fails and the prose is withheld. A shape-shaped promise broken silently is worse than the prose it replaced. An unusable schema — unparseable, or not an object schema — is refused before the request is sent, because a 400 from the vendor arrives later and explains less.

Narrowing a wide glob by what is in the files

You have to name the files before you know which ones matter — but working that out is part of what you are consulting the model for. include closes that gap from the cheap end: pass a wide glob and let the content decide.

glm-mcp ask -f 'src/**/*.ts' --include refreshToken "where can this leak?"

Only files whose content contains the literal text are sent. It is matched against what is in each file, never its path, so a file merely named for the term is dropped like any other non-match. It runs before the character budget, so a large file that is about to be discarded cannot crowd out a small one that matches — which is also why it cuts latency: every file it drops is prefill never paid for. The notes say how many were dropped, and if nothing matches, the call is refused rather than answered without the material it was about.

A literal substring, not a regular expression — deliberately. A caller-supplied pattern run over file contents would open the same ReDoS surface this server already keeps a gate for, in a new place. glm_ask takes the same parameter over MCP.

Asking from a shell

The same package is a one-shot CLI. It is the same code the MCP tools call — same confinement, same budget, same per-model ceilings — reached by argv instead of a client:

glm-mcp ask -f 'src/**/*.ts' --reasoning high "where can this invariant break?"
glm-mcp ask --model glm-4.6v --image screenshot.png "does this match the code?"
glm-mcp ask --include refreshToken -f 'src/**/*.ts' "where can this leak?"
glm-mcp models          # ids, with what each one is for
glm-mcp key             # which of the three sources the key resolves from
glm-mcp key --print     # the key itself, for a script that needs it

stdout is the answer and nothing else — notes, refusals and the cost line go to stderr — so ANSWER=$(glm-mcp ask ...) is exactly the answer. With no subcommand the same binary is the stdio MCP server it has always been, and unrecognised arguments still start the server, because MCP clients pass flags of their own.

Files are read through the same confinement as the server: a path outside GLM_MCP_ROOTS is refused here exactly as it is over MCP. This is not a second implementation — it is buildFileContext, which enforces the roots itself.

glm-mcp key exists so bin/claude-glm stops resolving the key a second time in bash. Those two copies had already drifted: this server requires GLM_MCP_ALLOW_ZCODE_KEY=1 before it will use the key ZCode stores, and the shell copy read that file unconditionally, which made the wrapper quietly more permissive than the server it wraps. Bare key prints the source rather than the secret, so an accidental invocation is not a leak.

This does not replace claude-glm. That turns GLM into an agent with tool use and editing; this is a single question and a single answer. Different jobs.

Writing a spec the loop can hold you to

Two of the worst defects this toolchain has shipped came from a single ambiguous sentence in a spec, resolved silently and plausibly by the implementer. Neither was caught by its acceptance gate, because the same person wrote the gate and the spec in the same words on the same afternoon — correlated oracles, not independent ones.

  • "normalise baseURL before comparing (trailing slash, empty string treated as unset)" bundles three decisions into one clause: what counts as equivalent, whether normalising is in place or scoped to the comparison, and what "unset" implies downstream. The normalisation landed on the value that gets sent, and a bearer token went to the vendor's default endpoint.

  • "whether a URL can be MADE of it" — "it" has two referents. Read as the joined URL, http:// passes, because http:// + v1/messages parses, and the token goes to a host called v1.

Both are mechanically detectable before dispatch: a pronoun whose referent is ambiguous, and a compound instruction bundling several decisions into one clause. Read your spec back looking for those two shapes specifically. It is cheaper than finding them in review, and far cheaper than finding them in production.

Three habits that follow:

  • Name the observable, not the mechanism. "the caller receives X when it sends Y", never "the function normalises Z" — the second specifies a design and cannot be checked from outside.

  • Say which value a rule applies to when a value is transformed on its way somewhere. "the value used for comparison" and "the value sent" are different things and the defect above lives in the gap.

  • Ask for the assumption. glm-task's notes channel has an assumption level below scope: it never interrupts anyone and lands in the ledger, and the standing instruction asks the agent to log any sentence it read one particular way, with the reading it chose. It is exempt from the channel's usual "default to silence" rule, because a resolved ambiguity does not feel like a fork from the inside — it feels like reading.

For a change where being wrong is expensive, pass -S to glm-review: a PASS from a single reviewer is one model's opinion, and -S refuses to call that reviewed.

Dogfooding is a step, not a good intention

Across one 33-run session the glm_ask consultant was used substantively three times, and those three calls produced three real defects — the routing guidance that sends callers to models the file-context budget was not sized for, reasoning: "none" selecting the opposite of what it says, and a README example citing a line number the model could not have counted. Eighteen acceptance gates were green across the same work.

Gates cannot substitute for this, and that is the point: every gate checks a property somebody already thought of. All three findings are of the form the tool documents or implies something it cannot do, which is invisible to a test written from the same understanding that produced the defect.

So after a caller-facing change lands, put the new surface in front of the model and ask it to use it, not to review the diff. "Route these six requests using only this guidance" is a usage question; "is this diff correct" is not, and only the first found #59. File what comes back, and verify it before acting — one of the model's own retro claims about this toolchain was simply wrong.

The shell tools in bin/

This repository holds two things that share a name and almost nothing else, and conflating them is the fastest way to be confused by it:

what it is

what GLM is

the MCP server — glm_ask, glm_review, glm_models

a server another agent calls mid-task

a tool the agent consults, keeping the wheel

bin/ — eleven commands, below

shell around GLM agent sessions

the agent, doing the work in the repository

These commands are not in the npm package. npm install @nocompromiseai/glm-mcp — or the npx registration above — gives you the MCP server and nothing else: files ships dist/, the README and the lockfile, deliberately, because an npm consumer has no CLI for these to drive and no reason to carry them. To get them, clone the repository and put its bin/ on your PATH:

git clone https://github.com/No-Compromise-AI/glm-mcp.git
cd glm-mcp && npm ci          # the postinstall also installs the delegation worker
export PATH="$PWD/bin:$PATH"  # or symlink the commands into ~/.local/bin

The MCP server and these commands are independent: you can register the server from npm and never clone, and the server does not need bin/ to work. What you cannot do is reach the delegation loop from an npm install alone, which is the thing that was previously undocumented (#89).

bin/claude-glm points the Claude Code CLI at z.ai, so GLM drives an interactive session. Delegation does not go through it: bin/glm-task hands the task to bin/glm-worker, which runs the Claude Agent SDK — a binary the worker package vendors, so the delegate → review → answer path needs no host CLI installed (#90) — verifies the result independently, has a different vendor's model review it against the spec, and records the outcome. The record says what a run identifies, not only what happened: the branch it worked on — the one -W/-b created, or the branch an in-place run stands on — and the issue, when the task names one as #123, issue 123, or the gh issue view 123 line. dir alone names a temp worktree that is deleted afterwards (#77). Reviewer selection follows the agent you are sitting in — GLM_HOST — so the host driving a delegation is never its own reviewer: from Claude that is codex + agy, from Codex claude + agy, from Antigravity codex + claude. -r all asks all three.

The commands

claude-glm

points the Claude Code CLI at z.ai, so GLM drives an interactive session. The delegation path does not use it — it needs no host CLI

glm-worker

the headless agent the delegation path runs: the Claude Agent SDK against z.ai, vendored in packages/worker so delegating needs no Claude Code installed. npm ci at the repo root installs it (that is the postinstall); by hand: npm ci --prefix packages/worker

glm-task

delegate one task: run the agent, verify independently, have a different vendor's model review it against the spec, one fix round, record the outcome

glm-drain

work a list of GitHub issues unattended — glm-task per item, glm-ship for what passes, parking for what needs you

glm-review

review a change against the spec it was meant to implement, with a chosen reviewer or auto

glm-ship

take a branch to merged-and-verified: PR, auto-merge, watch checks

glm-answer

resume a run that stopped to ask something, with your decision

glm-notes

what delegated runs have raised for you, with the resume command for each

glm-stats

hit rate, turns and duration across recorded runs

glm-why

a run's visible reasoning and tool trace

glm-gc

remove finished delegation worktrees. Only ever touches glm-wt-*, and never anything with uncommitted work

What a delegation reports, and what its exit code means

glm-task's exit code is the whole outcome, and each value means something different. Read it; a script that treats non-zero as one thing loses most of what the toolchain knows.

exit

meaning

what to do

0

done, verified, reviewed

ship it

1

verify failed, or the reviewer required changes

a change exists and was judged wanting

2

the review did not complete — UNREVIEWED, not passed

never ship on trust

3

every reviewer was out of capacity — also unreviewed

wait for capacity, or decide

4

the agent is BLOCKED and asked something

answer with glm-answer <session-id>

5

z.ai refused on capacity — the run was not attempted

retry; it says nothing about the change

6

the delegated agent did not complete

there is nothing to ship — read worker_reason

Exit 6 is why the other six are worth reading. Until 2026-08-31 a worker that could not start at all made glm-task exit 0 with a ledger row of nulls, indistinguishable from a run that succeeded — and glm-drain ships on a zero exit. It now covers a worker that never started, one that died, one whose result says it failed, one whose exit status contradicts its own result, and a run that finished and committed nothing (glm-ship pushes commits, so uncommitted work is discarded with the worktree). worker_reason says which, and names the files when work was left behind.

Each run appends a row to $GLM_TASK_LEDGER recording, besides the task and timings: branch and issue (so a run is attributable without re-reading its prose), worker_ok and worker_reason, verify/verify_rc, the review verdict and findings, and review_record — who reviewed with which model, and who did not deliver and why.

glm-ship reads that record before it merges. A branch whose own row says no reviewer passed it is refused, naming the verdict; -U overrides that and says so loudly. A branch with no row at all still ships — it is also the tool you use on work that never went through glm-task — and says no review record rather than staying silent, because "nobody reviewed this" and "a reviewer passed it" must not look the same.

It then posts that record to the pull request as a plain comment: the reviewers and their models, the exact base..head that was read, everyone who did not deliver and why, and a warning when HEAD moved after the review. It says in its own first line that it is self-reported, not third-party attestation.

Draining a backlog unattended

glm-drain -i 65,67,71 -v "npm test"

Each issue's title and body become the spec. What passes review is shipped; what does not is parked with its session id and worktree so glm-answer can resume it. The drain never attempts to answer a blocked item itself — a wrong guess made at 3am becomes a merged commit.

Two things it will not do, both deliberate. It refuses -j above 4 rather than silently clamping, because an operator who believes they are running nine wide and is actually running four has been told something untrue. And on any rate-limit signal it drops to one worker for the rest of the run and never widens again — backing off and immediately re-widening is the same evasion done more slowly. z.ai publishes no concurrency limit and returns no rate-limit headers, so staying conservative and reacting to pressure is the whole of what can honestly be promised.

Because merges are automatic: an unreviewed item is never shipped, shipping is serialised so each item's CI runs against a main containing the last, and consecutive failures stop the line rather than letting a broken main become the base for the rest of the night.

Knobs

which agent is driving, so it is never its own reviewer

GLM_HOST

detected for Claude; set it in Codex and Antigravity

default reviewer spec

GLM_REVIEWER

auto

the reviewer pool auto and all draw from

GLM_REVIEWER_POOL

codex claude agy

where auto lands when the host is unknown

GLM_AUTO_FALLBACK

codex

model agy reviews with

GLM_AGY_MODEL

claude-opus-4-6-thinking

reviewer deadline, and liveness tick

GLM_REVIEW_TIMEOUT / GLM_REVIEW_HEARTBEAT

900s / 60s

largest diff sent to a reviewer

GLM_REVIEW_MAX_DIFF

tool allowlist for the delegated agent

GLM_TASK_TOOLS

outcome ledger, and log path

GLM_TASK_LEDGER / GLM_TASK_LOG

~/.claude-glm/outcomes.jsonl

where glm-review writes its machine-readable record — who reviewed, with which model, who did not and why. glm-task sets this itself and folds the result into the ledger row as review_record; set it yourself to capture the same record when calling glm-review directly

GLM_REVIEW_RECORD

unset — no record is written

where -W worktrees are made

GLM_WORKTREE_ROOT

$TMPDIR

where a run writes what it wants you to know

GLM_NOTES

a per-run file in $TMPDIR

note levels that raise a desktop notification

GLM_NOTIFY_LEVELS

blocked

grace before concluding a PR has no CI

GLM_SHIP_CHECK_GRACE

1500s — measured; GitHub can take 2.5–20 min to create a run

drain: parked queue, first backoff step, halt threshold

GLM_DRAIN_STATE_DIR / GLM_DRAIN_BACKOFF_BASE / GLM_DRAIN_MAX_FAILS

~/.claude-glm / 60s / 2

drain: skip GitHub, treat items as opaque ids

GLM_DRAIN_OFFLINE

drain: override the commands it drives

GLM_TASK_CMD / GLM_SHIP_CMD

the siblings in bin/

These are not in the published npm package. This package's promise is the server; the shell family is the other half of the same idea, and it lives here because ~1,700 lines of working shell deserve a history. Use them from a checkout. One of them is heavy: glm-worker runs a ~199MB vendored binary, which is why it sits in packages/worker as its own package rather than a dependency of this one — npm ci at the repo root installs it via postinstall, so a fresh checkout can delegate with nothing further.

Reasoning

GLM-5.3 and glm-5.3-flash always reason. A request without a thinking block is rejected with z.ai error 1210 on GLM-5.3, so reasoning: "none" is silently raised to "low" for it. glm-5.3-flash cannot disable thinking either — z.ai's docs allow thinking.type no value but enabled, and sent disabled it still returns a thinking block without erroring — so its "none" is raised to "low" too, before anything is sent. glm-4.6 and glm-4.7 genuinely run with reasoning off.

"none" is a setting the request states, not the absence of one. z.ai documents an omitted thinking parameter as defaulting to enabled, so for two versions of this package "none" and unspecified were the same thing and the answer came back reasoned either way — silently, with nothing in the footer to tell a reasoned answer from one that was meant to skip reasoning. The request now says disabled out loud.

reasoning

thinking budget

none

sent as an explicit disabled block — the request states it, never omits thinking

low

2,048 tokens

high

8,192 tokens

max

24,576 tokens

max_tokens is a ceiling, not a target: the request never asks for more than you set. Where the budget above would not fit beneath it, the thinking budget is reduced to leave room for the answer — so a small max_tokens buys less reasoning rather than a bigger bill.

Set it below what any reasoning needs at all — the API's minimum thinking budget plus room for an answer — and the call is refused rather than quietly enlarged. The refusal names the smallest cap that would work. Raise max_tokens, or lower reasoning on a model that permits it.

Path confinement

glm_ask reads only inside roots the operator sets. A caller may narrow within them; it can neither choose nor escape them.

  • Roots come from GLM_MCP_ROOTS, colon-separated absolute paths.

  • Unset, the root is the directory the server was started in. Claude starts one server per project, so each server is confined to its own project and most setups need no configuration at all.

  • cwd must resolve inside a root. If it does not the call is refused outright, not quietly narrowed to a root — a silently empty answer is worse than an error that says why.

  • Every file's real path must land inside a root, so a symlink inside the tree pointing outside resolves outside and is refused. This is checked before a glob walks, so a pattern rooted outside never traverses.

  • Refused paths appear in Notes exactly like missing files, naming the spelling you used. One refused entry never fails a call that also names good files.

Whatever the roots say, the server never reads its own credentials — ~/.config/zai/api-key, ~/.zcode/v2/config.json and /proc/self/environ — compared by resolved real path rather than by spelling.

GLM_MCP_ALLOW_ANY_PATH=1 turns confinement off, deliberately and explicitly, the same way GLM_MCP_ALLOW_ZCODE_KEY works. It widens the roots; it does not re-open those three files.

Upgrading to 0.2.0

Using it in each of your projects needs no configuration. Your client starts one server per project, rooted at that project, so env: {} already gives you what you want: in ~/work/app it reads app, in ~/work/api it reads api.

If you read files across more than one project in a single call, set GLM_MCP_ROOTS before upgrading. That worked silently before 0.2.0 and is refused after it:

"env": { "GLM_MCP_ROOTS": "/Users/you/project-a:/Users/you/project-b" }

Absolute paths are confined too, but that breaks far less than it sounds: a survey of this author's own tooling found no caller that passes them.

Two other behaviours change in 0.2.0, both in your favour and neither needing configuration:

  • max_tokens is now a ceiling rather than a floor. It used to be raised to make room for reasoning, so a small cap silently became a large one. It is now honoured exactly, with the thinking budget scaled to fit beneath it. A cap too small to hold any reasoning and an answer is refused with a message naming the smallest that works, instead of quietly becoming an expensive request.

  • The defaults are much larger. Output goes from 8,192 to the model's own default — 65,536 for GLM-5.3 — and the file-context budget from 800,000 characters to roughly 2.9 million, derived from the context window rather than guessed. If you had raised GLM_MCP_MAX_FILE_CHARS to work around the old ceiling, you can drop the override.

glm_models gains ZAI_MODELS_URL. It matters only if you set ZAI_BASE_URL to control where your key is sent — see Errors and endpoints.

File context

files accepts literal paths and glob patterns, mixed freely. Matches are sorted and de-duplicated by file identity across the whole list, so overlapping patterns never send the same file twice.

When glm_ask is called with a thread (messages), the assembled context is attached to the thread's first turn rather than the newest, so the prefix stays cacheable across follow-ups — see Pushing back on an answer.

Every file arrives with line numbers, cat -n style — each line prefixed with its own number, so an answer like session.ts:88 reads expiresAt before taking the lock cites a line the model can actually see rather than one it had to count blind. The example answer above is a description of what comes back, not an aspiration.

A literal path may name a line range: src/auth/session.ts:40-120 sends lines 40 through 120, both ends inclusive, and nothing outside them. The excerpt keeps the file's numbering — those lines arrive numbered 40 to 120, never renumbered from 1 — so a citation against an excerpt points exactly where it would against the whole file. A range is how you send one region of a large file without the rest, which also cuts prefill and helps latency. A path that exists on disk is still read literally even when its name ends in something a range could be parsed out of: a real weird:10-20.txt is a filename, the same rule literal paths already have against glob characters. A range that names nothing readable is reported in Notes in your own spelling, exactly like a missing file.

Supported syntax: *, **, ?, [a-z], [!a-z], {a,b}, and \ escapes.

  • . and .. resolve against cwd, so ./src/** and ../neighbour/src/** work.

  • A path that exists on disk is read literally even if its name contains metacharacters — a real report[final].md is read, not pattern-matched.

  • Hidden (dot) entries match only when the pattern spells the dot out.

  • A pattern matching nothing is reported in Notes, exactly like a missing file — and when the call supplied no cwd and nothing else it asked for arrived either, that report also says the search ran in the directory the server was started in and names cwd and GLM_MCP_ROOTS as the knobs, so a mis-launched server (see the per-host table above) is diagnosable from the reply instead of reading as an ordinary no-match. The knobs are companions, not alternatives: cwd moves the search, and GLM_MCP_ROOTS widens the roots the new cwd must resolve inside — a cwd outside the roots is refused until it is widened. The extra sentence belongs to a call that delivered nothing: a duplicate beside its own delivered match, or a no-match beside files that did arrive, reads as the plain no-match it always was, as do a missing file and an empty line range — sending a caller who already has its files to look elsewhere would point at the wrong fix. The note names no path — which directory the server started in is the machine's business, not the caller's.

  • Symlinked directories are followed only when the pattern names one explicitly (linked/*.ts). Wildcards never follow them, and a link to a directory is never listed as a file.

  • On Windows, use forward slashes: C:/src/**/*.ts and //server/share/src/*.ts are honoured as absolute. \ is the escape character on every platform.

What globs skip

Glob expansion skips node_modules, .git, dist, build, coverage, .next, .turbo, vendor and target, so **/*.ts matches your source rather than 1,700 dependency type definitions crowding out the budget.

This applies to expansion only — a literal node_modules/foo/x.d.ts goes through untouched. Naming a directory in the pattern also overrides the skip, because the caller asked for it: node_modules/foo/**/*.d.ts matches as expected.

Set GLM_MCP_GLOB_IGNORE to a comma-separated list to replace the default set (GLM_MCP_GLOB_IGNORE=dist,.venv); an empty value disables skipping entirely.

Limits

Every limit stops the operation that hit it and says so in Notes, naming the variable that set it — nothing is ever silently truncated or silently dropped.

Limit

Variable

Default

Total context characters, headers and separators included

GLM_MCP_MAX_FILE_CHARS

derived per model — ~2.9M for GLM-5.3

Context window the budget is derived from

GLM_MCP_CONTEXT_TOKENS

the model's declared window; 1,048,576 when undeclared

Per-file size, checked before the file is read

GLM_MCP_MAX_FILE_BYTES

5 MB

Glob walk depth

GLM_MCP_MAX_DEPTH

24

Directory entries examined per call

GLM_MCP_MAX_ENTRIES

200,000

Wall-clock budget for glob expansion

GLM_MCP_GLOB_TIMEOUT_MS

10,000

Total {a,b} brace expansions

GLM_MCP_MAX_BRACE_EXPANSIONS

1,024

Request timeout, the whole call — all retries included

GLM_MCP_TIMEOUT_MS

600,000

Heartbeat interval for progress notifications on a long call

GLM_MCP_PROGRESS_MS

5,000

Least analysis a glm_review verdict may stand on, in characters of the reply beside the verdict line

GLM_REVIEW_MIN_SUBSTANCE

200

The character budget is derived, per model, not chosen: the context window of the model the request will use, less that model's default output and a prompt reserve, at 3 characters per token. The windows come from the same z.ai-published table that sizes output — 1,048,576 for GLM-5.3 and glm-5.3-flash, 200,000 for glm-4.7 and glm-4.6, 128,000 for glm-4.5 and glm-4.6v — so a caller routed to a cheaper model by the routing guidance is sized against that model's window, not the flagship's million tokens. Two models sharing a window still get different budgets when their output defaults differ: glm-4.5 and glm-4.6v both have 128K, but glm-4.6v holds back a quarter as much for the reply, so more of the window is left for your files. A model the table does not know keeps the documented assumption of 1,048,576 — z.ai is the authority on its own models, exactly as with output ceilings. A truncation note names which bound cut it, and names the window by its source — the model's own published window, the window you set with GLM_MCP_CONTEXT_TOKENS, or the documented assumption for a model whose window nobody has recorded — because only the first of those is a fact about the model. Your explicit GLM_MCP_MAX_FILE_CHARS cap is named as itself.

A thread's prior turns (messages to glm_ask) spend this same budget before the first file is read, so a long thread leaves less room for file context — and the truncation note says how much the history spent when it is what crowded the files.

That ratio targets English and code deliberately — this is what the project is built for. Denser scripts pack more tokens per character (Chinese runs nearer one token per character), so a CJK-heavy caller should lower GLM_MCP_MAX_FILE_CHARS; overshooting surfaces as a context-length error from z.ai rather than a silent truncation. GLM_MCP_CONTEXT_TOKENS overrides the window for every model at once — a published figure that proves wrong is corrected with one variable, not a code change — and GLM_MCP_MAX_FILE_CHARS overrides the character budget outright, also for every model.

GLM_MCP_GLOB_TIMEOUT_MS bounds the work a walk does, not the time it can spend blocked inside a syscall. The deadline is checked between operations — on the way into each directory, and every 64th entry within a scan — so a big walk tests it thousands of times, but never during an operation: a readdirSync that has not returned cannot be interrupted by it. A hung FUSE, NFS or SMB mount is exactly that case, and it is the one an operator reading the timeout as a wall-clock guarantee would be wrong about: the walk freezes mid-call with most of its budget unspent, and nothing fires until the syscall comes back — if it comes back. The timeout bounds what a walk does, never what it waits on.

File reads are synchronous, so a call that reads a tree delays every other call on the same server until it finishes. The walk, the reads and the assembly around them are readdirSync / statSync / readFileSync, and they block the event loop; the round trip to z.ai is asynchronous. It is specifically the filesystem half that does not interleave: two calls with no files, issued together against an upstream deliberately held at 300 ms per call, finished in 306 ms rather than 600. With files it is the other way round — measured through the real MCP surface against an upstream that answers instantly, so every millisecond is the server's own doing, a call reading 400 files (79 MB) took 680 ms while a trivial call issued beside it, one that reads nothing and answers in 3 ms alone, took 637 ms and finished with the read rather than on its own schedule. That is over 200× its own latency, all of it spent waiting on another caller's context, and it is the number to weigh before running one shared server for several agents: their calls serialise behind each other's file context, and the limits above are all that bound how long one read holds the rest. Two things about that 400-file run are worth knowing when you size your own cap. It needed GLM_MCP_MAX_FILE_CHARS raised to 100,000,000 for the run — the default derived budget would have cut the same walk far sooner — and even 100,000,000 is less generous than it looks, because the cap measures the text delivered, not the bytes read: line numbers and headers swell 79 MB of files to 85.2M characters of prompt, so a cap sized to the bytes on disk would cut this walk at the 372nd file and never read the last 28. Size the cap to the prompt you are willing to pay for, not to the tree.

Output ceilings come from z.ai's published table and are per model — 131,072 for the GLM-5 and 4.6/4.7 families, 98,304 for GLM-4.5, 32,768 for the vision models. Asking for more than a model allows is refused locally, naming the ceiling, rather than discovered as an API error. A model this table does not know is not capped here: z.ai's own limits govern it.

GLM_MCP_TIMEOUT_MS bounds the whole call, including retries — not one attempt. Retryable failures (408/409/429/5xx, connection errors) are retried twice with the SDK's own backoff, and the one number you set covers every attempt together: at the default, a call ends after ten minutes however many attempts it has made, not thirty. The first attempt is entitled to nearly the whole budget — the output default is 65,536 tokens and long single calls are the normal case, so the budget is never divided to reserve room for retries that may never happen. One residual: the SDK's backoff sleep between attempts is not abort-aware, so a deadline that fires during a backoff wait overshoots by up to that sleep — bounded by whatever retry-after the server sent.

A call that long is otherwise a black box, so glm_ask and glm_review send MCP progress notifications while the model works — one every GLM_MCP_PROGRESS_MS (five seconds by default), each carrying the time elapsed, and only to a client that asked by passing a progress token with the request. Unsolicited progress is a protocol violation rather than a courtesy; a client that sends no token gets none. The heartbeat is time-based, not token-based: what it tells you is that the call is still alive, which is what you cannot otherwise tell — tokens-so-far would need the request switched to streaming. It stops when the call ends, however it ends, and it changes nothing about the answer: same text, same usage footer, same notes.

  • Only regular files are read. A FIFO, device or socket is refused rather than blocking the server on a read that may never return.

  • Truncation cuts on code points, so it never splits an emoji in half.

  • Missing, unreadable and refused files are skipped and reported, never fatal: one bad entry does not fail a call that also names good files.

Errors and endpoints

z.ai's coded errors are translated into something actionable: 1113 (no balance), 1210 (reasoning required), 3007 (wrong credential type — see Credentials above). The code has to be the error's own code: a request id or token count that happens to contain those digits is passed through untranslated rather than explained as something it is not.

Requests go to https://api.z.ai/api/anthropic unless ZAI_BASE_URL says otherwise. Your key is sent to whatever host it names, so only point it at endpoints you trust. A ZAI_BASE_URL that is set but that no URL can be made of — which is what ZAI_BASE_URL="${HOST}/" with HOST unset leaves — makes the server refuse to start with an error naming the variable, rather than quietly falling back to the default: the variable is how egress is scoped, so a value you set is sent as written or refused, never swapped for a host you did not name.

glm_models reads a different endpoint on a different path prefix, so it does not follow ZAI_BASE_URL — pointing the two at one host would only be a guess about your gateway's layout. It defaults to https://api.z.ai/api/paas/v4/models and is set explicitly with ZAI_MODELS_URL. If you set ZAI_BASE_URL to scope where your key is sent, set ZAI_MODELS_URL too, or glm_models will keep sending the key to z.ai. Both requests observe GLM_MCP_TIMEOUT_MS as a whole-call budget.

Testing

npm test      # unit tests: globs, key resolution, confinement, limits, delegation
npm run       # every `verify:*` script listed is an acceptance gate
npm run smoke # drives the server over stdio as a real MCP client (needs a key)

Every verify:* script in package.json is an acceptance gate, and CI runs all of them on Node 20, 22 and 24 — that is what verify:release checks, so a gate added without being wired into both workflows fails the build. They are listed there rather than here on purpose: a partial list in prose reads as a complete one, and this section carried five of them for long enough to stop being true.

smoke makes live API calls, so it is run by hand; everything else is hermetic.

Each acceptance gate was written before the change it gates and failed against the code it was written for, so it asserts the behaviour rather than describing it. They build real fixture trees — real files, real symlinks, a real FIFO, a real fake $HOME — and run against child processes where a setting has to be in place before the module loads.

Releases

npm-shrinkwrap.json carries the version too, so a release bump regenerates it (npm shrinkwrap) alongside package.json. verify:supplychain fails if the two drift, in CI and again at release, so it cannot ship half-done.

Published from CI with provenance via npm trusted publishing — there is no long-lived npm token. Every release is staged and requires a maintainer to approve it with 2FA before it becomes installable, and its provenance ties the published tarball to this repository and the workflow that built it.

Author

Built by Jerold Billings, Founder — No Compromise AI, LLC.

Bugs and questions: open an issue. Security problems: please use

Available Tools

3 tools
glm_askAsk GLMA

Send a prompt to a Z.ai GLM model (default GLM-5.3) and return its answer. GLM-5.3 is an independent frontier model with a million-token context window, so this is useful for a genuine second opinion from a different model, for cross-checking reasoning, and for analysing far more source material at once than fits in a normal context. Optionally pass file paths to include as context. Model and reasoning are the latency levers: thinking tokens are generated before the first character of the answer, and the thinking budget spans 2,048 at 'low' against 24,576 at 'max' — a twelve-fold spread. Route mechanical work (extract, summarise, reformat, classify) to glm-5.3-flash or glm-4.6 at 'low'; glm-4.6 alone can go further, to 'none' — glm-5.3-flash cannot run with reasoning off, so its 'none' is raised to 'low'. Keep GLM-5.3 at 'high' or 'max' for design review, cross-checking reasoning, and hunting a subtle bug. glm-4.6 and glm-4.7 accept reasoning 'none'; GLM-5.3 and glm-5.3-flash cannot, so 'low' is their shallowest setting.

ParametersJSON Schema
NameRequiredDescriptionDefault
cwdNoDirectory that relative file paths resolve against. Defaults to the server's cwd.
filesNoOptional files to include as context: literal paths and/or glob patterns (e.g. "src/**/*.ts"). Each glob expands to its matching files, sorted and de-duplicated across the whole list; a pattern that matches nothing is reported in the response notes. A path that exists on disk is used literally even when it contains glob characters. Glob expansion skips node_modules, .git and build output by default; naming a directory in the pattern (node_modules/foo/**/*.d.ts) or setting GLM_MCP_GLOB_IGNORE overrides that. Relative paths — ./ and ../ prefixes included — resolve against 'cwd'. Every file arrives with cat -n style line numbers, so answers can cite path:line and mean it; a literal path may carry an inclusive line range ("src/auth/session.ts:40-120") to send just that region, numbered with the file's own line numbers rather than renumbered from 1.
modelNoGLM model id. Defaults to glm-5.3 (the frontier flagship); glm-5.3-flash and glm-4.6 are the fast routes, and glm_models lists every id the account offers with a one-line role.
promptYesThe question or instruction to send to GLM.
systemNoOptional system prompt.
messagesNoThe conversation so far: prior turns this call continues, in order, each {role, content}. `prompt` stays required and is sent as the FINAL user turn — do not repeat it inside messages. Roles are "user" and "assistant"; any other is refused here, before anything is sent, naming the value you sent. No ordering is imposed — replay a real transcript as it happened. With `files`, the file context rides the FIRST turn and is never repeated on the newest, so the thread keeps a stable prefix: a follow-up reads its context from cache instead of re-prefilling it. The history spends the same character budget as the files, so a long thread leaves less room for file context — the cut is reported in the notes.
reasoningNoReasoning depth — the largest latency lever in this tool: thinking tokens are generated before the first character of the answer, and the budget runs 2,048 at 'low', 8,192 at 'high', 24,576 at 'max'. Use 'none' or 'low' for mechanical work — extract, summarise, reformat; use 'high' or 'max' to review a design, cross-check reasoning, or hunt a subtle bug. GLM-5.3 and glm-5.3-flash always reason: GLM-5.3 rejects 'none' outright, while glm-5.3-flash accepts it and silently reasons anyway, so 'none' is raised to 'low' for both.
max_tokensNoMax output tokens — a hard cap. The request never exceeds it; the thinking budget scales down to fit beneath it, always leaving room for the answer, but never below the API minimum of 1024. A cap below 2048 — the API's budget minimum plus the least room that still constitutes an answer — cannot hold both and is refused rather than silently raised; on GLM-5.3 and glm-5.3-flash, which always reason, the only fix is a higher cap. A cap over the model's published ceiling is likewise refused before anything is sent (131,072 for GLM-5.3). Omit it and the model's own default applies (65,536 for GLM-5.3).

TDQS

A4.1/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 disclosing behavior, and it is exceptionally thorough. It explains thinking-token generation before the answer, reasoning budget ranges, model-specific constraints ('glm-5.3-flash cannot run with reasoning off, so its none is raised to low'), file context placement on the first turn, history consuming character budget, and max_tokens cap behavior including refusals. This is far beyond a basic safety profile.

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?

The description is long but information-dense; every sentence carries either a use-case, a latency lever, or a routing rule. It is front-loaded with the core action and value proposition before diving into details. Some redundancy with the schema's reasoning and model descriptions exists, but for a tool this complex the length is justified.

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?

The description covers operational nuances such as file globbing behavior, line numbers, message ordering, reasoning constraints, and max_tokens caps. It mentions response notes for unmatched globs and history cuts, which implies a structured return. It does not fully spell out the output format, but for a chat-completion tool 'return its answer' plus the notes mention is reasonably complete given the lack of an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and each parameter already has rich documentation, so the baseline is 3. The tool description adds high-level guidance about model/reasoning selection, but it mostly reinforces what is already in the parameter schemas rather than introducing new parameter-level meaning. The schema descriptions alone are sufficient for parameter understanding.

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 opens with a specific verb and resource: 'Send a prompt to a Z.ai GLM model (default GLM-5.3) and return its answer.' It clearly explains the tool's function and even suggests use cases. However, it does not explicitly differentiate from the sibling tools glm_review and glm_models, relying on the tool name and general context to separate them.

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 gives clear context for when to use the tool: 'for a genuine second opinion from a different model, for cross-checking reasoning, and for analysing far more source material at once than fits in a normal context.' It also provides detailed routing advice among models and reasoning levels, e.g., 'Route mechanical work (extract, summarise, reformat, classify) to glm-5.3-flash or glm-4.6 at low.' It does not explicitly say when to use glm_review or glm_models instead, so no true alternatives are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

glm_modelsList GLM modelsA

List the GLM model ids available on the configured Z.ai account, each with a one-line role; an id this server's model table does not know is listed bare.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It discloses useful behavior: each model id is annotated with a one-line role, and unknown ids are rendered bare. It does not mention authentication or rate limits, but for a simple read-only enumeration this is not a significant gap.

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 sentence that front-loads the action and includes only essential details about the output format and the special handling of unknown ids. Every clause adds value with no redundancy.

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 zero-parameter list tool with no output schema, this description is complete: it tells the agent what will be returned, how roles appear, and how unknown ids are presented. Nothing critical is missing for invoking and interpreting the tool.

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 an empty input schema, so there is nothing for the description to clarify. The baseline of 4 applies because the schema already fully covers parameter semantics.

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: lists GLM model ids from the configured Z.ai account. It also adds useful output semantics (one-line role, unknown ids listed bare), which makes it clearly distinct from sibling tools glm_ask and glm_review.

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 makes the context clear: use this tool when you need to enumerate available model ids. It does not explicitly name alternatives or when-not-to-use, but the sibling tool names and the list-oriented wording make the intended usage obvious enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

glm_reviewReview with GLMA

Review a change with a Z.ai GLM model (default GLM-5.3) and return a VERDICT: the reply is the reviewer's analysis and always ends with a final line that is exactly VERDICT: PASS or VERDICT: CHANGES_REQUIRED — the same vocabulary bin/glm-review reads, so a shell pipeline can consume the result. Pass the change as a unified diff and the requirement it was meant to implement as spec: review against intent is what catches silent scope-narrowing, and the reviewer is warned off both recorded pathologies — findings that are padded or fabricated, and work that is stubbed, mocked or hardcoded rather than implemented. A reply that is a bare verdict with no analysis behind it comes back as an error, never as a clean review. This server never runs git and inspects no repository state on its own: the diff comes from the caller, and files resolve exactly as glm_ask resolves them. Reviews default to reasoning 'high' — the depth the glm_ask routing guidance reserves for review and bug-hunting — and a different model than the one that wrote the code is worth choosing where you can, because a model re-reading its own work reliably under-reports.

ParametersJSON Schema
NameRequiredDescriptionDefault
cwdNoDirectory that relative file paths resolve against. Defaults to the server's cwd.
diffNoThe unified diff to review, as your tooling produced it. The server never runs git — the caller supplies the change under review, and this argument is how. Either diff or files must be present; with neither, the call is refused rather than answered with a verdict about nothing.
specNoWhat the change was meant to do — the requirement, ticket or plan it was written against. Reaches the reviewer verbatim. Review against intent is the only check on silent scope-narrowing, this loop's recorded failure mode; with no spec the reviewer can only infer intent from the diff itself.
filesNoOptional files as review context, resolved exactly as glm_ask resolves them (same confinement to the operator's roots, same per-model character budget, same notes): literal paths and/or glob patterns (e.g. "src/**/*.ts"). Each glob expands to its matching files, sorted and de-duplicated across the whole list; a pattern that matches nothing is reported in the response notes. A path that exists on disk is used literally even when it contains glob characters. Glob expansion skips node_modules, .git and build output by default; naming a directory in the pattern (node_modules/foo/**/*.d.ts) or setting GLM_MCP_GLOB_IGNORE overrides that. Relative paths — ./ and ../ prefixes included — resolve against 'cwd'.
modelNoGLM model id. Defaults to glm-5.3 (the frontier flagship); glm-5.3-flash and glm-4.6 are the fast routes, and glm_models lists every id the account offers with a one-line role.
reasoningNoReasoning depth — same levels as glm_ask, but the default here is 'high' rather than 'low': a review is the work the routing guidance reserves 'high' for, and a reviewer skimming on the 2,048-token 'low' budget is the rubber stamp with extra steps. Use 'max' (24,576 tokens) for a large or subtle change, and 'low' only for a re-check you expect to be mechanical. GLM-5.3 and glm-5.3-flash always reason, so 'low' is their shallowest setting.
max_tokensNoMax output tokens — a hard cap. The request never exceeds it; the thinking budget scales down to fit beneath it, always leaving room for the answer, but never below the API minimum of 1024. A cap below 2048 — the API's budget minimum plus the least room that still constitutes an answer — cannot hold both and is refused rather than silently raised; on GLM-5.3 and glm-5.3-flash, which always reason, the only fix is a higher cap. A cap over the model's published ceiling is likewise refused before anything is sent (131,072 for GLM-5.3). Omit it and the model's own default applies (65,536 for GLM-5.3). A review severed by too small a cap loses its verdict line and is returned as an error, so size it for the analysis plus the verdict.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With zero annotations, the description carries the full burden and discharges it thoroughly: it discloses the exact verdict line grammar, that a bare verdict is returned as an error, that the server never runs git and inspects no repository state, that calls with neither diff nor files are refused, and that the reviewer is explicitly warned against padded/fabricated findings and stubbed/mocked/hardcoded work. This is rich behavioral disclosure well beyond what any structured field provides.

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?

The description is long but front-loaded with the most critical fact — the exact VERDICT contract — before any parameter framing. Every sentence carries real content, from refusal behavior to the reasoning-depth default to the model-advice caveat. It is dense prose rather than concise prose, and a few points repeat what the schema already says, but nothing is filler.

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 7-parameter tool with no annotations and no output schema, this description is nearly complete: it specifies the return contract, error/refusal conditions, default model and reasoning level, cross-tool file-resolution semantics, and both recorded failure modes the reviewer is guarded against. The only deferrals are reasonable ones — glob-ignore overrides and character budgets live in the files parameter schema, and depth beyond routing is delegated to glm_ask's guidance.

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 schema's own parameter descriptions are already unusually detailed, so the baseline is 3. The description adds genuine value on top: the rationale for the diff+spec pairing ('review against intent is what catches silent scope-narrowing') and the model-selection heuristic that a model re-reading its own work under-reports, which appears in no schema field. Some default and reasoning-guidance content is duplicated between description and schema, keeping this at 4 rather than 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?

The description states a specific verb and resource — 'Review a change with a Z.ai GLM model' — and defines a concrete, distinctive output contract: a reply ending in exactly 'VERDICT: PASS' or 'VERDICT: CHANGES_REQUIRED'. This clearly distinguishes it from siblings glm_ask (asking) and glm_models (listing models) through the review-specific verdict vocabulary and the diff+spec input pairing.

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 places glm_review within the glm_ask routing guidance ('the depth the glm_ask routing guidance reserves for review and bug-hunting') and gives actionable advice — supply a diff against spec rather than just a diff, and choose a different model than the one that wrote the code. However, it never explicitly states when to prefer glm_review over glm_ask or vice versa; that routing is inferred from the sibling names and the verdict contract rather than stated outright.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A4.4/5.0
Disambiguation4/5

glm_ask and glm_review both send prompts to GLM, which could cause some overlap, but glm_review's strict VERDICT format and diff/spec input make its purpose clearly distinct. glm_models is wholly separate.

Naming Consistency4/5

glm_ask and glm_review follow a consistent verb-first pattern, while glm_models breaks it by using a noun instead of a verb like list_models. Minor deviation, but the prefix keeps the family recognizable.

Tool Count5/5

Three tools is within the ideal 3-15 range and each tool earns its place: one for general prompting, one for structured review, and one for model discovery. The scope is tightly focused.

Completeness5/5

For a GLM-oriented server, the surface covers the core needs: asking questions, reviewing changes against a spec, and listing available models. No obvious dead ends or missing operations within the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    Gives Claude access to multiple AI models (Gemini, OpenAI, OpenRouter, Ollama) for enhanced development capabilities including extended reasoning, collaborative development, code review, and advanced debugging.
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables Claude Code (Anthropic Sonnet) to invoke Z.AI's GLM-4.6 model through a secondary Claude instance. Supports code generation, deep analysis, and general queries while maintaining file tracking and secure token management.
    1
  • A
    license
    Not graded
    quality
    D
    maintenance
    Exposes local Ollama instances as tools for Claude Code, allowing users to offload code generation, text drafting, and embedding tasks to local GPUs. It supports multi-turn conversations and model management through the Model Context Protocol.
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/No-Compromise-AI/glm-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server