The top-level tree, with each server's package name and registry, is in
AGENTS.md under Project structure. This skill is the
level below it: what is inside each server, and where a new file goes. Test
placement and harnesses are /testing; building and running a server is
/local-dev.
There is no shared library between servers. Each directory under src/ is
a complete package that versions and publishes on its own, so a helper two
servers need is written in both (expandHome exists in filesystem and in
memory, and the copy in memory says so). Do not add a cross-server import.
The TypeScript servers
All four are npm workspaces of the root package.json and share one shape: the
entry point is index.ts, sources sit at the workspace root (there is no
src/ inside a server), tests are in __tests__/, and tsc writes dist/.
Each has the same four config files: package.json, tsconfig.json (the
build), tsconfig.test.json (the no-emit typecheck program, which also
includes the tests) and vitest.config.ts.
| Server | Layout | Where its features are registered |
|---|---|---|
everything |
index.ts picks a transport from argv; server/ holds the factory; tools/, resources/, prompts/ hold one file per feature plus an index.ts; transports/ holds one file per transport; version.ts reads the package version; docs/ is shipped |
Each feature file exports a register… function, wired into its area's index.ts (registerTools, registerResources, registerPrompts), which server/index.ts calls from createServer() |
filesystem |
index.ts is the stdio entry point (argv → allowed directories, main()); server.ts exports createServer(allowedDirectories); lib.ts holds the file operations and path validation, with the allow-list passed in rather than held globally; path-utils.ts, path-validation.ts and roots-utils.ts are focused helpers; version.ts reads the package version |
Inline in createServer() in server.ts, with server.registerTool(...) |
memory |
index.ts holds the KnowledgeGraphManager, createServer(memoryFilePath) and the stdio main(); version.ts reads the package version |
Inline in createServer(); the resource and its subscription handlers are exported register… functions in the same file |
sequentialthinking |
index.ts holds createServer() and the stdio main(); lib.ts holds SequentialThinkingServer, the logic; version.ts reads the package version |
Inline in createServer() |
Three things about everything that the table cannot hold:
server/index.tsexportscreateServer(), which returns{ server, cleanup }. Every transport file calls it, once per stdio process or once per HTTP session. A tool that depends on what the client can do (roots, sampling, elicitation) is registered throughregisterConditionalTools, which runs afterinitialize, not throughregisterTools.docs/is part of the server, not only documentation. The build copies it intodist/docs, every file in it is served as a static resource, anddocs/instructions.mdis returned as the server'sinstructions. A file added there becomes a resource a client can list.- Its own maps are
src/everything/docs/structure.mdandsrc/everything/docs/extension.md. A change to the server's files updatesstructure.md, andsrc/everything/AGENTS.mdholds the rules that apply only inside that directory.
No server starts on import. Each one builds its server in a
createServer(...) factory, and its entry file connects a transport only when
it is the process entry point (an isEntryPoint() or isMainModule() check
that compares real paths, so the npm bin symlink still starts it). Keep it that
way: a module that binds a port, connects stdio or installs a signal handler at
import cannot be driven in-process by the tests. /testing covers the
in-process harness.
The Python servers
fetch, git and time are independent uv projects with the same layout:
src/<server>/
├── pyproject.toml dependencies, the console script, pytest and coverage config
├── uv.lock, .python-version
├── src/mcp_server_<name>/
│ ├── __init__.py main(): the command-line flags, then asyncio.run(serve(...))
│ ├── __main__.py python -m mcp_server_<name>
│ └── server.py everything else
└── tests/ pytest suite, driving serve() in-process
server.py holds the Pydantic input models, the functions that do the work,
and serve(). serve() constructs the Server, declares the tools (and, in
fetch, the prompts) with the @server.list_tools() / @server.call_tool()
decorators inside its body, and then runs it over stdio. So a new tool is a
set of edits in server.py: its input model, its entry in list_tools, and
its branch in call_tool. In git and time there is a fourth: tool names
are members of an enum (GitTools, TimeTools) that list_tools and
call_tool both dispatch on, so the new name is added there first. fetch
has one tool and no enum. The Server object is local to serve() and not
importable.
Where to put a new file
| It is… | It goes in |
|---|---|
A tool, resource or prompt for everything |
Its own kebab-case file in tools/, resources/ or prompts/, exporting a register… function, wired into that directory's index.ts |
A transport for everything |
src/everything/transports/, plus a case in src/everything/index.ts and a start:… script |
A tool for filesystem, memory or sequentialthinking |
A server.registerTool(...) call inside that server's createServer() (server.ts in filesystem, index.ts in the other two); the logic it calls goes in lib.ts (or the class it extends) |
A helper module for everything |
A kebab-case .ts file in the feature area it supports, beside its users (server/logging.ts, server/roots.ts, resources/session.ts), imported with the .js extension |
A helper module for filesystem, memory or sequentialthinking |
A kebab-case .ts file at the workspace root, imported with the .js extension |
| A tool for a Python server | server.py: the tool-name enum member (git, time), the model, the list_tools entry, the call_tool branch |
| A command-line flag for a Python server | __init__.py, passed into serve() |
| A test | /testing |
| Root tooling (a guard, a release helper) | scripts/<name>.mjs, with a sibling <name>.test.mjs when it has logic worth testing; shared helpers in scripts/lib/ |
| A design document | docs/ |
| A procedure for agents | .claude/skills/<name>/SKILL.md, with a row in the Skills index |
A new server directory is not on this list: the repository does not accept new
servers (CONTRIBUTING.md).
What a new file is checked against
A TypeScript file in a new location is still inside the gate, because two
guards enumerate tracked files rather than trusting a glob:
verify:typecheck-coverage fails when a tracked .ts file is in no project
that typecheck runs, and verify:format-coverage fails when a tracked source
file matches no prettier --check glob. Both run in npm run validate:guards.
If one fires on a file you added, the fix is to bring the file into the
existing config, not to exclude it.
When a file or directory is added, removed, renamed or given a different
purpose, every entry that describes it changes in the same PR (AGENTS.md,
Maintenance rules): the root README.md, the server's own README.md,
the tree in AGENTS.md, and for everything, docs/structure.md.