Skip to main content
Glama
John0615

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