10. Project-Specific Commands
Run all commands from the repository root. This project uses
pnpmversion9.12.1.
Environment Setup
Local Development
Type Checking
Documentation
README.md and skills/<bin>/SKILL.md are hand-written — there is no generation
command. Editing a tool means editing both files by hand. See
08-file-creation-rules for the checklist.
Documentation Site (Rspress)
The docs site is driven by the root rspress.config.ts. Static docs are the .md files under docs/, and the tools API page (/api) is generated by readme-docs-plugin from the root README.md.
Tests
Coverage reports are written to coverage/.
Verification (same order as CI)
Releases are handled by release-please. There is no local release dry-run command — merging the release PR triggers the real release.
Running the MCP Server
pnpm start is a stdio server: it waits on stdin and prints nothing on its own. That is the
normal state — an MCP client drives it. To see it answer by hand, pipe JSON-RPC frames in:
Use the WHITE_FN / BLACK_FN env vars to restrict which tools are exposed (comma-separated, matched by tool name). A non-empty WHITE_FN acts as an allowlist.
Where Credentials Come From
The server and the CLI read TMDB_API_KEY / TMDB_ACCESS_TOKEN from the process environment,
in this order of precedence:
- Variables already set in the environment — the
envblock of an MCP client config, or an inline prefix such asTMDB_API_KEY=... pnpm start. - A
.envfile in the current working directory, loaded at startup. - A credential embedded into the bundle at build time (see below).
A value already present in the environment always wins over .env, and either of them wins over
the embedded credential. The environment is taken as a whole: as soon as it supplies TMDB_API_KEY
or TMDB_ACCESS_TOKEN, the embedded pair is ignored entirely rather than filling in the other
half — a bearer token is preferred over an API key, so mixing the two sources would silently
discard the key you passed in. When no source supplies a credential, every movie tool answers with
a single Error: ... line naming both variables.
Building a Bundle That Carries the Credential
pnpm build reads TMDB_API_KEY / TMDB_ACCESS_TOKEN from the build environment and writes the
value into the bundle, so the resulting server needs no credential to start:
The build prints which variables it embedded:
The value is sealed with AES-256-GCM rather than written in plain text, so it is not a greppable
string in dist/. The secret that opens it ships in the same bundle, which makes this obfuscation
and not encryption — anyone holding the bundle can recover the key. Treat any build made this way
as carrying a secret: do not commit it, do not attach it to a release, and do not npm publish it.
To rotate or remove the value, rebuild without the variable set.
In CI the same two variables come from repository secrets, wired into the Test step of
.github/workflows/ci.yml:
Add the secret under Settings → Secrets and variables → Actions. With it present, pnpm test
also exercises the tests that call TMDB for real; without it those tests skip and the rest of the
suite still passes. Pull requests opened from a fork never receive secrets, so they take the skip
path. Set SKIP_TMDB_LIVE_TESTS=1 to skip them even when a credential is available.
The Build steps in both workflows receive the same two secrets, because a credential handed to
the build is written into the bundle. In release.yml that is deliberate: the package published to
npm carries the credential, so npx @julong/mcp-kit needs nothing in the client config. The
consequence is that every install can recover the key — see
Build-Time TMDB Credentials for what that means for
which key belongs in the secret.
An MCP client spawns the server with a working directory you do not control, so
.envis only reliable when you start the server yourself. For a client config, use itsenvblock.
Connecting from LM Studio
LM Studio reads the same mcpServers shape (Program → Install → Edit mcp.json). Point it at the
built entry with an absolute path and pass the credential through env:
Run pnpm build first — the path above is the build output, not the source.
Running CLI Tools
Running the Movie Tools
The TMDB tools need a credential — TMDB_API_KEY (v3 API key) or TMDB_ACCESS_TOKEN (read access
token). Issue either at https://www.themoviedb.org/settings/api. The examples below pass it in
the environment; a build that already carries one (see "Building a Bundle That Carries the
Credential") runs the same commands with no prefix. No browser is involved, so Playwright is not
needed.
Passing null keeps a later argument positional — the CLI maps arguments to schema fields in
order. Without a credential every movie tool answers with a single Error: ... line instead of
throwing.
Agent Integration Test (MCP + LLM)
Runs the MCP server under an LLM agent (deepagents + langchain) and checks the
tool results end to end. The whole cycle is written to src/common/.log/<taskId>.log.
pnpm test:agent runs src/agent.test.ts, which asserts that the MCP server exposes both
tools, that the LLM actually invoked one of them, that the tool arguments match the request
scope, and that the numbers in the final answer came from the tool result rather than from
the model. Plain pnpm test skips the file unless RUN_AGENT_TESTS is set.
The test is the only place that wires an agent: @/common/agent supplies the model, the
logging and nodeMcpServer(), while the test supplies the MCP server to spawn and the
prompts. The published bundle stays a pure tool provider and never imports langchain.
The agent needs network access, a valid API key, and Playwright's browser (
npx playwright install chromium). It is not part ofpnpm test.
Git Commits
Publishing
Bundle Inspection
Troubleshooting
MCP error -32000: Connection closed
This error means the client's stdio pipe to the server is gone — the server process exited. The server writes every log line to stderr, so read the client's MCP log for this server first.
Reproduce the handshake outside the client to separate a client problem from a server problem:
One call to exchange_rates is budgeted at min(timeoutMs × (currencies + 1), 45s) and returns whatever it has read when the budget runs out — currencies it could not reach in time come back as null. The 45-second cap is deliberate: an MCP client waits 60 seconds for one request by default, and a call that runs past that returns no partial result at all. Adding a currency lengthens a call but can no longer push it over that line.