Skip to main content
Glama
xmkoh

langchain-docs-local-mcp

by xmkoh
README.md
# langchain-docs-local-mcp

An offline MCP server that provides LangChain and LangGraph documentation search — a drop-in replacement for the cloud-hosted `mcp__langchain-docs__` server for airgapped environments.

## Why

In airgapped environments the cloud MCP server for LangChain docs is unreachable. This project bundles the documentation locally and serves it via a local MCP server using BM25 full-text search — no network access, no ML model downloads, no Docker required at runtime.

## Tools exposed

| Tool | Description |
|------|-------------|
| `search_docs_by_lang_chain` | BM25 search across bundled LangChain/LangGraph docs |
| `query_docs_filesystem_docs_by_lang_chain` | Shell-like access to doc files (`cat`, `head`, `ls`, `find`, `rg`) |

These names match the original cloud MCP server exactly for drop-in compatibility.

---

## Local quick start (stdio)

```bash
# 1. Clone and install
git clone https://github.com/xmkoh/langchain-docs-local-mcp
cd langchain-docs-local-mcp
pip install -r requirements.txt

# 2. Build the search index from the bundled docs
python scripts/build_index.py

# 3. Test
pip install pytest
pytest tests/

# 4. Wire up Claude Code — edit the path, then restart Claude Code
cp .claude/settings.json /path/to/your/project/.claude/settings.json
```

`.claude/settings.json` (stdio):

```json
{
  "mcpServers": {
    "langchain-docs": {
      "type": "stdio",
      "command": "python",
      "args": ["server.py"],
      "cwd": "/absolute/path/to/langchain-docs-local-mcp"
    }
  }
}
```

The tools appear in Claude Code as:
- `mcp__langchain-docs__search_docs_by_lang_chain`
- `mcp__langchain-docs__query_docs_filesystem_docs_by_lang_chain`

---

## Kubernetes / OpenShift deployment

### 1. Build and push the image

The image bakes the docs and pre-built index in at build time — no volumes or init containers needed.

```bash
docker build -t registry.example.com/langchain-docs-local-mcp:0.1.0 .
docker push registry.example.com/langchain-docs-local-mcp:0.1.0
```

The image defaults to **SSE transport** on port 8000. Switch to streamable-http via the `MCP_TRANSPORT` env var or `--transport` flag.

### 2. Deploy with Helm

```bash
# OpenShift — with a public Route (TLS edge termination)
helm install langchain-docs ./helm/langchain-docs-mcp \
  --namespace my-namespace \
  --set image.repository=registry.example.com/langchain-docs-local-mcp \
  --set image.tag=0.1.0 \
  --set route.enabled=true

# Plain Kubernetes — with an Ingress instead
helm install langchain-docs ./helm/langchain-docs-mcp \
  --namespace my-namespace \
  --set image.repository=registry.example.com/langchain-docs-local-mcp \
  --set image.tag=0.1.0 \
  --set ingress.enabled=true \
  --set ingress.hosts[0].host=langchain-docs-mcp.example.com \
  --set ingress.hosts[0].paths[0].path=/ \
  --set ingress.hosts[0].paths[0].pathType=Prefix
```

After install, Helm prints the Claude Code config snippet automatically (via `NOTES.txt`).

### 3. Get the Route URL (OpenShift)

```bash
oc get route langchain-docs-langchain-docs-mcp -n my-namespace \
  -o jsonpath='{.spec.host}'
```

### 4. Wire up Claude Code (network transport)

**SSE** (default, `mcpTransport: sse`):

```json
{
  "mcpServers": {
    "langchain-docs": {
      "type": "sse",
      "url": "https://<ROUTE_HOST>/sse"
    }
  }
}
```

**Streamable HTTP** (`--set mcpTransport=streamable-http`):

```json
{
  "mcpServers": {
    "langchain-docs": {
      "type": "http",
      "url": "https://<ROUTE_HOST>/mcp"
    }
  }
}
```

**Port-forward** (no Route/Ingress, in-cluster access only):

```bash
kubectl port-forward svc/langchain-docs-langchain-docs-mcp 8000:8000 -n my-namespace
```

```json
{
  "mcpServers": {
    "langchain-docs": {
      "type": "sse",
      "url": "http://localhost:8000/sse"
    }
  }
}
```

### OpenShift security notes

The image is built to work with the default **restricted** SCC — no `anyuid` or elevated permissions required:
- Files are owned `1001:0` with `chmod g=u` so OpenShift's arbitrary UID (always GID 0) can read them
- `runAsNonRoot: true`, `allowPrivilegeEscalation: false`, all capabilities dropped
- `readOnlyRootFilesystem: true` with an `/tmp` emptyDir for Python's tempfile

### Key Helm values

| Value | Default | Description |
|-------|---------|-------------|
| `image.repository` | `langchain-docs-local-mcp` | Image to deploy |
| `image.tag` | chart `appVersion` | Image tag |
| `mcpTransport` | `sse` | `sse` or `streamable-http` |
| `route.enabled` | `false` | Create an OpenShift Route |
| `route.tls.enabled` | `true` | TLS on the Route |
| `route.tls.termination` | `edge` | `edge`, `passthrough`, or `reencrypt` |
| `ingress.enabled` | `false` | Create a Kubernetes Ingress |
| `autoscaling.enabled` | `false` | Enable HorizontalPodAutoscaler |
| `replicaCount` | `1` | Number of replicas |

---

## Updating the documentation

Documentation is bundled at image build time. To update after an upstream LangChain/LangGraph release:

```bash
# On a machine with internet access:
pip install httpx
python scripts/fetch_docs.py       # re-downloads from GitHub
python scripts/build_index.py      # rebuilds index/bm25_index.pkl

git add docs/ index/
git commit -m "Update docs"
git push

# Then rebuild and push a new image:
docker build -t registry.example.com/langchain-docs-local-mcp:0.2.0 .
docker push registry.example.com/langchain-docs-local-mcp:0.2.0
helm upgrade langchain-docs ./helm/langchain-docs-mcp --set image.tag=0.2.0
```

`fetch_docs.py` prints a warning for any URL that fails — this happens when LangChain reorganizes their docs. Update the `FETCH_TARGETS` list in `scripts/fetch_docs.py` with the new GitHub raw URL.

## Adding your own documentation

Drop any `.md` file anywhere under `docs/` and rebuild the index and image:

```bash
python scripts/build_index.py
docker build -t registry.example.com/langchain-docs-local-mcp:custom .
```

## Offline pip installs (fully airgapped)

```bash
# On a networked machine — bundle wheels alongside the repo:
pip download -r requirements.txt -d wheels/
git add wheels/ && git commit -m "Add offline wheels"

# On the airgapped machine:
pip install --no-index --find-links wheels/ -r requirements.txt
```

## Documentation included

The repository ships with hand-curated documentation for:

- **LangGraph**: `BaseStore`, `InMemoryStore`, `MemorySaver`, `StateGraph`, checkpointers, persistence, human-in-the-loop
- **Deep Agents**: architecture overview, memory patterns (short-term vs long-term), namespace conventions
- **LangChain**: `@tool`, `StructuredTool`, `BaseTool`, `create_react_agent`, `AgentExecutor`, streaming

See [`docs/README.md`](docs/README.md) for full coverage.

## Architecture

```
server.py              FastMCP server — stdio, SSE, or streamable-http transport
search.py              BM25 search engine (rank_bm25), loaded once at startup
Dockerfile             Builds a self-contained image with docs + index baked in
helm/
  langchain-docs-mcp/  Helm chart for Kubernetes / OpenShift
    templates/
      deployment.yaml  Pod with restricted SCC security context
      service.yaml     ClusterIP on port 8000
      route.yaml       OpenShift Route (route.enabled=true)
      ingress.yaml     Kubernetes Ingress (ingress.enabled=true)
      hpa.yaml         HorizontalPodAutoscaler (autoscaling.enabled=true)
docs/                  Bundled markdown documentation
index/
  bm25_index.pkl       Pre-built search index (committed to git)
scripts/
  build_index.py       Rebuild the index from docs/
  fetch_docs.py        Download docs from GitHub (run once, online)
```