Skip to main content
skill CX Codex

project-structure · modelcontextprotocol — Claude Code skill: where a new file goes

This skill maps the layout of the modelcontextprotocol/servers repo and tells you which file or directory a new piece of code belongs in.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add modelcontextprotocol/servers --skill project-structure -a claude-code
Open on GitHub

What project-structure does

The modelcontextprotocol/servers repo holds seven servers, each with its own layout. The skill covers the level below the top-level tree in AGENTS.md: what sits inside every server and where a new file goes — a tool, resource, prompt, transport, helper module, command-line flag or root script. Reach for it when a server's layout is unclear, or when you are weighing the entry file against a module of its own.

There is no shared library between servers. Every directory under src/ is a complete package that versions and publishes on its own, so a helper two servers need gets written in both: expandHome exists in filesystem and in memory. A cross-server import is the wrong move, and that is the first thing a tidy-up refactor gets wrong.

The four TypeScript servers are npm workspaces of the root package.json and share one shape: entry point index.ts, sources at the workspace root, tests in __tests__/, tsc writing dist/. The three Python servers are independent uv projects with server.py holding the Pydantic models, the logic and serve(). No server starts on import — each builds itself in a createServer(...) factory, and the transport connects only when the file is the process entry point.

What it helps with

  • find where tools are registered in the everything server
  • decide between the entry file and a separate module for a new tool
  • add a transport and wire the case in index.ts plus a start:… script
  • figure out why a helper is duplicated across two servers and keep it that way
  • place a new Python tool across the model, list_tools and call_tool

Example prompts for the agent

  • Where should a new tool for the everything server live?
  • I'm adding a command-line flag to a Python server — which file do I edit?
  • Help me choose between the entry file and a separate module for this code

How to install project-structure

1Skills CLI (Vercel)

npx skills add modelcontextprotocol/servers --skill project-structure -a claude-code

2Manually: put the files in place

~/.claude/skills/project-structure/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/project-structure/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about project-structure

How do I install project-structure in Claude?

Run npx skills add modelcontextprotocol/servers --skill project-structure -a claude-code. The skill lands in ~/.claude/skills/project-structure/SKILL.md.

What does project-structure do?

It explains how the repo's seven servers are laid out and points to the right spot for a new file: a tool, resource, prompt, transport, helper, flag or root script.

Can a shared helper live in one module for several servers?

No. Each directory under src/ is its own package, so a helper two servers need is written in both — expandHome in filesystem and memory is the example.

Source text

SKILL.md · Apache-2.0
---
name: project-structure
description: "Find where code lives in this repo's seven servers and decide where a new file goes. Use when choosing the file or directory for a new tool, resource, prompt, transport, helper module, command-line flag or root script; when a server's layout is unclear; or when asking whether code belongs in a server's entry file or a module of its own."
disable-model-invocation: false
---

# Project structure

The top-level tree, with each server's package name and registry, is in
[`AGENTS.md`](../../../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, and `pinned-directory.ts` runs the write operations inside their verified directory; `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.ts` exports `createServer()`**, 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 through
  `registerConditionalTools`, which runs after `initialize`, not through
  `registerTools`.
- **`docs/` is part of the server, not only documentation.** The build copies
  it into `dist/docs`, every file in it is served as a static resource, and
  `docs/instructions.md` is returned as the server's `instructions`. A file
  added there becomes a resource a client can list.
- **Its own maps are `src/everything/docs/structure.md` and
  `src/everything/docs/extension.md`.** A change to the server's files updates
  `structure.md`, and `src/everything/AGENTS.md` holds 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`.

© modelcontextprotocol · Apache-2.0 · modelcontextprotocol/servers

Similar skills

All skills for Agent setup: memory, context, orchestration →

project-structure by other authors comes first.

From the same set7

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor