mcp-readonly-code-server
by John0615
README.md
# mcp-readonly-code-server
Minimal Node + TypeScript example project for a read-only MCP server that exposes one local code workspace to an AI client over `stdio`.
## Project position
This repository is currently an example project, not a production-ready remote service.
It demonstrates how to:
- build a read-only MCP server with the official TypeScript SDK
- expose one local workspace root safely
- serve MCP resources and a simple search tool over `stdio`
- enforce basic deny rules for sensitive paths and file types
## What this project exposes
- Official MCP TypeScript SDK wired through `McpServer`
- `stdio` bootstrap entry for local MCP hosts
- Three read-only resources:
- `repo://overview`
- `repo://tree/{path}`
- `repo://file/{path}`
- One read-only tool:
- `search_code`
- A path guard that keeps all file access inside one workspace root
- A `sample-private-code/` directory for local smoke testing
## Current behavior
This build exposes a real read-only code workspace through MCP resources plus one minimal search tool:
- `repo://overview` explains the boundary and available surface
- `repo://tree/{path}` lists files and directories under an allowed subtree
- `repo://file/{path}` reads one allowed text file
- `search_code` recursively searches text files and returns line-level matches
Safety rules:
- all access stays inside `WORKSPACE_ROOT`
- denied directories: `.git`, `node_modules`, `dist`, `coverage`
- denied suffixes: `.env`, `.pem`, `.key`, `.crt`
- binary and oversized files are rejected
## Install
```bash
npm install
```
## Run
Start the server in development mode:
```bash
npm run dev
```
By default, it exposes this sample directory:
```bash
sample-private-code/
```
## Specify the project directory
Use `WORKSPACE_ROOT` to choose which local project the MCP server exposes.
Expose the sample directory explicitly:
```bash
WORKSPACE_ROOT=/home/zsp0509/node-projects/mcp-readonly-code-server/sample-private-code npm run dev
```
Expose this repository itself:
```bash
WORKSPACE_ROOT=/home/zsp0509/node-projects/mcp-readonly-code-server npm run dev
```
Expose another project:
```bash
WORKSPACE_ROOT=/path/to/your-project npm run dev
```
If the path contains spaces, quote it:
```bash
WORKSPACE_ROOT="/home/zsp0509/My Projects/app" npm run dev
```
Notes:
- use an absolute path
- one server instance exposes one workspace root
- if you need multiple projects, configure multiple MCP server entries with different `WORKSPACE_ROOT` values
## How agents call it
This project is a `stdio` MCP server.
That means:
- it does not open an HTTP port
- an MCP host starts the process directly
- the host communicates with it through `stdin` and `stdout`
There are two common ways to use it:
### 1. Manual local run
You start it yourself in a terminal:
```bash
WORKSPACE_ROOT=/path/to/your-project npm run dev
```
In this mode, the process must keep running. If you stop it, the agent cannot call it.
### 2. Host-managed run
You register it in an MCP-capable host such as an inspector or desktop client.
In this mode, the host usually starts the process automatically when needed. You do not need to keep a separate terminal open.
## Example MCP host configuration
Development command:
```json
{
"command": "node",
"args": [
"--import",
"tsx",
"/home/zsp0509/node-projects/mcp-readonly-code-server/src/index.ts"
],
"env": {
"WORKSPACE_ROOT": "/path/to/your-project"
}
}
```
Built command:
```json
{
"command": "node",
"args": [
"/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js"
],
"env": {
"WORKSPACE_ROOT": "/path/to/your-project"
}
}
```
## Multiple project host configuration
If you want one agent host to access multiple projects, register multiple MCP server entries.
Each entry uses the same server program but a different `WORKSPACE_ROOT`.
Example:
```json
{
"mcpServers": {
"crm-code": {
"command": "node",
"args": [
"/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/crm"
}
},
"admin-panel-code": {
"command": "node",
"args": [
"/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/admin-panel"
}
}
}
}
```
In that setup:
- `crm-code` exposes only `/srv/projects/crm`
- `admin-panel-code` exposes only `/srv/projects/admin-panel`
- each server process keeps its own workspace boundary
You can do the same with the development entrypoint:
```json
{
"mcpServers": {
"crm-code-dev": {
"command": "node",
"args": [
"--import",
"tsx",
"/home/zsp0509/node-projects/mcp-readonly-code-server/src/index.ts"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/crm"
}
},
"admin-panel-code-dev": {
"command": "node",
"args": [
"--import",
"tsx",
"/home/zsp0509/node-projects/mcp-readonly-code-server/src/index.ts"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/admin-panel"
}
}
}
}
```
Use different server names so the host can distinguish them clearly.
## Common host examples
Different MCP hosts may wrap server definitions differently, but the important part stays the same:
- the command points to this server
- each project gets its own server entry
- each entry sets a different `WORKSPACE_ROOT`
### Claude Desktop style
Some hosts use a top-level `mcpServers` object like this:
```json
{
"mcpServers": {
"crm-code": {
"command": "node",
"args": [
"/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/crm"
}
},
"admin-panel-code": {
"command": "node",
"args": [
"/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/admin-panel"
}
}
}
}
```
If you want to use the TypeScript entry during development, replace the command arguments with:
```json
[
"--import",
"tsx",
"/home/zsp0509/node-projects/mcp-readonly-code-server/src/index.ts"
]
```
### Cherry Studio style
If your host asks you to add one MCP server at a time in a form or list UI, create two separate local command entries:
Server 1:
```json
{
"name": "crm-code",
"command": "node",
"args": [
"/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/crm"
}
}
```
Server 2:
```json
{
"name": "admin-panel-code",
"command": "node",
"args": [
"/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js"
],
"env": {
"WORKSPACE_ROOT": "/srv/projects/admin-panel"
}
}
```
If the UI exposes separate fields instead of raw JSON, fill them like this:
- `Name`: `crm-code`
- `Command`: `node`
- `Args`: `/home/zsp0509/node-projects/mcp-readonly-code-server/dist/src/index.js`
- `WORKSPACE_ROOT`: `/srv/projects/crm`
Then add a second entry with a different name and project path.
### Practical notes
- prefer the built entrypoint `dist/src/index.js` for long-term use
- use the `src/index.ts` entrypoint mainly for local development
- if your host uses a different outer JSON shape, keep the inner `command`, `args`, and `env.WORKSPACE_ROOT` values the same
## Build
Build the TypeScript output:
```bash
npm run build
```
Run the built server:
```bash
WORKSPACE_ROOT=/path/to/your-project npm run start
```
## Smoke test
Run the in-process MCP client validation script:
```bash
npm run smoke
```
It validates:
- `repo://overview`
- `repo://tree/{+path}`
- `repo://file/{+path}`
- `search_code`
- denied-path rejection for `.git/config`
## Inspector checklist
If you want to verify the real `stdio` workflow with an MCP inspector or host:
1. Start the server or register the command in your host.
2. Point the inspector or host command at:
```bash
node --import tsx src/index.ts
```
3. Set `WORKSPACE_ROOT` to the project you want to expose.
4. Verify these calls:
- read `repo://overview`
- read `repo://tree/controllers`
- read `repo://file/controllers/user-controller.ts`
- call `search_code` with `{"query":"return","path":"controllers"}`
- try denied path `repo://file/.git/config`
## Limitations
Current scope:
- `stdio` transport only
- one workspace root per process
- read-only resources and one minimal text search tool
If you want to deploy this on a remote server for agents on other machines, you would typically add an HTTP-based MCP transport in a follow-up implementation.
TDQS
B3.3/5.0
Scored across 1 tool
Disambiguation5/5
With only one tool, there is no possibility of confusion between tools. The single tool's purpose is clearly defined.
Naming Consistency5/5
The sole tool 'search_code' follows a clear verb_noun pattern, making its purpose immediately understandable. Consistency is trivially maintained.
Tool Count2/5
A read-only code server with only one tool feels excessively limited. Typically, such a server would need multiple tools (e.g., list files, read file content) to be useful, making a single tool insufficient for the apparent scope.
Completeness1/5
The toolset is severely incomplete for a read-only code server. Essential operations like listing files or reading file contents are missing, leaving agents unable to perform basic code exploration tasks.
Maintenance
ActivitySlowing
ResponsivenessNo issues