Skip to content

Commit 9f35c54

Browse files
committed
feat(docker): add configurable storage path & improve support
This commit significantly enhances Docker support and introduces a configurable storage path for the database, simplifying installation and deployment. - **Configurable Storage:** Introduced the `DOCS_MCP_STORE_PATH` environment variable to allow users to specify a custom directory for the `documents.db` file. The path resolution order is now: 1. `DOCS_MCP_STORE_PATH` (if set) 2. Legacy `./.store/` directory (if `documents.db` exists there) 3. Standard OS-specific application data directory - **Docker Enhancements:** - Updated the Dockerfile to use `node:22-slim`, expose a `/data` volume, and set `DOCS_MCP_STORE_PATH=/data`. - Added a new GitHub Actions workflow (`docker-publish.yml`) to build and publish the Docker image to GHCR. - Updated the README.md to recommend Docker as the primary installation method, providing clear instructions and an updated MCP configuration example. - Refined `.dockerignore` for a smaller build context. - **CI/CD Updates:** Removed `--ignore-scripts` from `npm ci` commands in workflows to align with Docker build process. - **Dependency Management:** Updated `prepare` script in `package.json` for better compatibility (`husky || true`).
1 parent 527d9f9 commit 9f35c54

File tree

11 files changed

+202
-57
lines changed

11 files changed

+202
-57
lines changed

.clineignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dist/
55
*.log
66
.env.*
77
!.env.example
8+
!.github/

.dockerignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ Dockerfile
44

55
# Version control
66
.git
7+
.github
8+
.husky
79

810
# Already in gitignore but explicitly listed for Docker context
911
node_modules
1012
dist
1113
*.log
1214
.env*
15+
16+
# Other
17+
.store
18+
README.md
19+
ARCHITECTURE.md
20+
CHANGELOG.md

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
# OpenAI
22
OPENAI_API_KEY=your-key-here
3+
4+
# Optional: Specify a custom directory to store the SQLite database file (documents.db).
5+
# If set, this path takes precedence over the default locations.
6+
# Default behavior (if unset):
7+
# 1. Uses './.store/' in the project root if it exists (legacy).
8+
# 2. Falls back to OS-specific data directory (e.g., ~/Library/Application Support/docs-mcp-server on macOS).
9+
# DOCS_MCP_STORE_PATH=/path/to/your/desired/storage/directory

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
cache: 'npm'
2222

2323
- name: Install dependencies
24-
run: npm ci --ignore-scripts
24+
run: npm ci
2525

2626
- name: Run linter
2727
run: npm run lint
@@ -41,7 +41,7 @@ jobs:
4141
cache: 'npm'
4242

4343
- name: Install dependencies
44-
run: npm ci --ignore-scripts
44+
run: npm ci
4545

4646
- name: Run tests
4747
run: npm run test
@@ -61,7 +61,7 @@ jobs:
6161
cache: 'npm'
6262

6363
- name: Install dependencies
64-
run: npm ci --ignore-scripts
64+
run: npm ci
6565

6666
- name: Run build
6767
run: npm run build
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# .github/workflows/docker-publish.yml
2+
name: Docker Publish
3+
4+
on:
5+
push:
6+
tags:
7+
- 'v*.*.*' # Trigger only on version tags like v1.0.0, v1.2.3, etc.
8+
9+
jobs:
10+
publish:
11+
name: Build and Push Docker Image to GHCR
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read # Needed to check out the code
15+
packages: write # Needed to push Docker image to GHCR
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Log in to GitHub Container Registry
22+
uses: docker/login-action@v3
23+
with:
24+
registry: ghcr.io
25+
username: ${{ github.actor }} # Use the GitHub username initiating the workflow
26+
password: ${{ secrets.GITHUB_TOKEN }} # Use the automatically generated token
27+
28+
- name: Extract Docker metadata
29+
id: meta
30+
uses: docker/metadata-action@v5
31+
with:
32+
images: ghcr.io/${{ github.repository }} # Use ghcr.io/arabold/docs-mcp-server
33+
tags: |
34+
# Extract version number from the Git tag (e.g., v1.2.3 -> 1.2.3)
35+
type=semver,pattern={{version}}
36+
# Also tag with 'v' prefix (e.g., v1.2.3)
37+
type=semver,pattern={{raw}}
38+
# Add the 'latest' tag
39+
type=raw,value=latest,enable={{is_default_branch}} # Only add latest tag if the tag is on the default branch (usually main) - semantic-release should ensure this
40+
41+
- name: Build and push Docker image
42+
uses: docker/build-push-action@v5
43+
with:
44+
context: .
45+
push: true
46+
tags: ${{ steps.meta.outputs.tags }}
47+
labels: ${{ steps.meta.outputs.labels }}
48+
cache-from: type=gha
49+
cache-to: type=gha,mode=max

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
cache: 'npm'
3030

3131
- name: Install dependencies
32-
run: npm ci --ignore-scripts
32+
run: npm ci
3333

3434
- name: Run build
3535
run: npm run build

Dockerfile

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# Build stage
2-
FROM node:20-alpine AS builder
2+
FROM node:22-slim AS builder
33

44
WORKDIR /app
55

66
# Copy package files
77
COPY package*.json ./
88

99
# Install dependencies
10-
RUN npm ci --ignore-scripts
10+
RUN npm ci
1111

1212
# Copy source code
1313
COPY . .
@@ -16,20 +16,22 @@ COPY . .
1616
RUN npm run build
1717

1818
# Production stage
19-
FROM node:20-alpine
19+
FROM node:22-slim
2020

2121
WORKDIR /app
2222

2323
# Copy package files
2424
COPY package*.json ./
2525

2626
# Install production dependencies only
27-
RUN npm ci --omit=dev --ignore-scripts
27+
RUN npm ci --omit=dev
2828

2929
# Copy built files from builder
3030
COPY --from=builder /app/dist ./dist
3131

32+
# Define the data directory environment variable and volume
33+
ENV DOCS_MCP_STORE_PATH=/data
34+
VOLUME /data
35+
3236
# Set the command to run the application
33-
# CMD ["node", "dist/index.cjs"]
34-
EXPOSE 8000
35-
CMD ["npx", "-y", "supergateway", "--stdio", "node", "dist/server.js"]
37+
CMD ["node", "dist/server.js"]

README.md

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,27 @@ Install the package globally using npm. This makes the `docs-server` and `docs-c
4242
4343
This method is convenient if you plan to use the `docs-cli` frequently.
4444
45-
### Method 2: Direct Execution with `npx` (Recommended for MCP Integration)
45+
### Method 2: Running with Docker (Recommended for MCP Integration)
4646
47-
Run the server or CLI directly using `npx` without needing a global installation. The `-y` flag ensures the package is automatically downloaded if needed.
47+
Run the server using the pre-built Docker image available on GitHub Container Registry. This provides an isolated environment and simplifies setup.
4848
49-
1. **Run the Server (e.g., for MCP Integration):**
49+
1. **Ensure Docker is installed and running.**
50+
2. **Run the Server (e.g., for MCP Integration):**
5051
5152
```bash
52-
npx -y --package=@arabold/docs-mcp-server docs-server
53+
docker run -i --rm \
54+
-e OPENAI_API_KEY="your-openai-api-key-here" \
55+
-v docs-mcp-data:/data \
56+
ghcr.io/arabold/docs-mcp-server:latest
5357
```
5458
55-
This is the recommended approach for integrating with tools like Claude Desktop or Cline, as it avoids polluting the global namespace and ensures the correct version is used.
59+
- `-i`: Keep STDIN open, crucial for MCP communication over stdio.
60+
- `--rm`: Automatically remove the container when it exits.
61+
- `-e OPENAI_API_KEY="..."`: **Required.** Set your OpenAI API key.
62+
- `-v docs-mcp-data:/data`: **Required for persistence.** Mounts a Docker named volume (`docs-mcp-data` is created automatically if it doesn't exist) to the container's `/data` directory, where the database is stored. You can replace `docs-mcp-data` with a specific host path if preferred (e.g., `-v /path/on/host:/data`).
63+
- `ghcr.io/arabold/docs-mcp-server:latest`: Specifies the public Docker image to use.
64+
65+
This is the recommended approach for integrating with tools like Claude Desktop or Cline.
5666
5767
**Claude/Cline Configuration Example:**
5868
Add the following configuration block to your MCP settings file (adjust path as needed):
@@ -65,8 +75,17 @@ Run the server or CLI directly using `npx` without needing a global installation
6575
{
6676
"mcpServers": {
6777
"docs-mcp-server": {
68-
"command": "npx",
69-
"args": ["-y", "--package=@arabold/docs-mcp-server", "docs-server"],
78+
"command": "docker",
79+
"args": [
80+
"run",
81+
"-i",
82+
"--rm",
83+
"-e",
84+
"OPENAI_API_KEY",
85+
"-v",
86+
"docs-mcp-data:/data",
87+
"ghcr.io/arabold/docs-mcp-server:latest"
88+
],
7089
"env": {
7190
"OPENAI_API_KEY": "sk-proj-..." // Required: Replace with your key
7291
},
@@ -80,13 +99,18 @@ Run the server or CLI directly using `npx` without needing a global installation
8099
81100
Remember to replace `"sk-proj-..."` with your actual OpenAI API key and restart the application.
82101
83-
2. **Run the CLI:**
102+
3. **Run the CLI (Requires Docker):**
103+
To use the CLI commands, you can run them inside a temporary container:
84104
```bash
85-
npx -y --package=@arabold/docs-mcp-server docs-cli <command> [options]
105+
docker run --rm \
106+
-e OPENAI_API_KEY="your-openai-api-key-here" \
107+
-v docs-mcp-data:/data \
108+
ghcr.io/arabold/docs-mcp-server:latest \
109+
npx docs-cli <command> [options]
86110
```
87111
(See "CLI Command Reference" below for available commands and options.)
88112
89-
This method is ideal for one-off executions or when integrating the server into other tools.
113+
This method is ideal for integrating the server into other tools and ensures a consistent runtime environment.
90114
91115
## CLI Command Reference
92116
@@ -220,9 +244,9 @@ docs-cli remove react --version 18.2.0
220244
221245
## Development & Advanced Setup
222246
223-
This section covers running the server/CLI using Docker or directly from the source code for development purposes.
247+
This section covers running the server/CLI directly from the source code for development purposes. The primary usage method is now via the public Docker image as described in "Method 2".
224248
225-
### Running with Docker
249+
### Running from Source (Development)
226250
227251
This provides an isolated environment and exposes the server via HTTP endpoints.
228252
@@ -244,20 +268,22 @@ This provides an isolated environment and exposes the server via HTTP endpoints.
244268
4. **Run the Docker container:**
245269
246270
```bash
247-
docker run -p 8000:8000 --env-file .env --name docs-mcp-server-container docs-mcp-server
271+
# Option 1: Using a named volume (recommended)
272+
# Docker automatically creates the volume 'docs-mcp-data' if it doesn't exist on first run.
273+
docker run -i --env-file .env -v docs-mcp-data:/data --name docs-mcp-server docs-mcp-server
274+
275+
# Option 2: Mapping to a host directory
276+
# docker run -i --env-file .env -v /path/on/your/host:/data --name docs-mcp-server docs-mcp-server
248277
```
249278
250-
- `-p 8000:8000`: Maps host port 8000 to container port 8000.
251-
- `--env-file .env`: Loads environment variables from your local `.env`.
252-
- `--name docs-mcp-server-container`: Assigns a container name.
279+
- `-i`: Keep STDIN open even if not attached. This is crucial for interacting with the server over stdio.
280+
- `--env-file .env`: Loads environment variables (like `OPENAI_API_KEY`) from your local `.env` file.
281+
- `-v docs-mcp-data:/data` or `-v /path/on/your/host:/data`: **Crucial for persistence.** This mounts a Docker named volume (Docker creates `docs-mcp-data` automatically if needed) or a host directory to the `/data` directory inside the container. The `/data` directory is where the server stores its `documents.db` file (as configured by `DOCS_MCP_STORE_PATH` in the Dockerfile). This ensures your indexed documentation persists even if the container is stopped or removed.
282+
- `--name docs-mcp-server`: Assigns a convenient name to the container.
253283
254-
5. **Available Endpoints:**
255-
- SSE: `http://localhost:8000/sse`
256-
- POST Messages: `http://localhost:8000/message`
284+
The server inside the container now runs directly using Node.js and communicates over **stdio**.
257285
258-
### Running from Source (Development)
259-
260-
This method is required for contributing to the project or running un-published versions.
286+
This method is useful for contributing to the project or running un-published versions.
261287
262288
1. **Clone the repository:**
263289
```bash
@@ -290,8 +316,17 @@ This method is required for contributing to the project or running un-published
290316
cp .env.example .env
291317
```
292318
2. Update your OpenAI API key in `.env`:
319+
293320
```
321+
# Required: Your OpenAI API key for generating embeddings.
294322
OPENAI_API_KEY=your-api-key-here
323+
324+
# Optional: Specify a custom directory to store the SQLite database file (documents.db).
325+
# If set, this path takes precedence over the default locations.
326+
# Default behavior (if unset):
327+
# 1. Uses './.store/' in the project root if it exists (legacy).
328+
# 2. Falls back to OS-specific data directory (e.g., ~/Library/Application Support/docs-mcp-server on macOS).
329+
# DOCS_MCP_STORE_PATH=/path/to/your/desired/storage/directory
295330
```
296331
297332
### Debugging (from Source)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"package.json"
2020
],
2121
"scripts": {
22-
"prepare": "husky",
22+
"prepare": "husky || true",
2323
"build": "tsup",
2424
"cli": "node --enable-source-maps dist/cli.js",
2525
"start": "node --enable-source-maps dist/server.js",

src/store/DocumentManagementService.test.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,6 @@ vi.mock("./DocumentRetrieverService", () => ({
5353
DocumentRetrieverService: vi.fn().mockImplementation(() => mockRetriever),
5454
}));
5555

56-
vi.mock("../utils/logger", () => ({
57-
logger: {
58-
info: vi.fn(),
59-
warn: vi.fn(),
60-
error: vi.fn(),
61-
},
62-
}));
63-
6456
// --- END MOCKS ---
6557

6658
describe("DocumentManagementService", () => {
@@ -86,7 +78,7 @@ describe("DocumentManagementService", () => {
8678
await docService?.shutdown();
8779
});
8880

89-
// --- NEW: Constructor Path Logic Tests ---
81+
// --- Constructor Path Logic Tests ---
9082
describe("Constructor Database Path Selection", () => {
9183
// Add beforeEach specific to this suite for memfs reset
9284
beforeEach(() => {
@@ -125,6 +117,36 @@ describe("DocumentManagementService", () => {
125117
// Verify the standard directory was created in memfs
126118
expect(vol.existsSync(path.dirname(expectedStandardDbPath))).toBe(true);
127119
});
120+
121+
it("should use the path from DOCS_MCP_STORE_PATH environment variable if set", () => {
122+
const mockEnvStorePath = "/mock/env/store/path";
123+
const expectedEnvDbPath = path.join(mockEnvStorePath, "documents.db");
124+
const originalEnvValue = process.env.DOCS_MCP_STORE_PATH; // Store original value
125+
process.env.DOCS_MCP_STORE_PATH = mockEnvStorePath; // Set env var
126+
127+
try {
128+
// Ensure neither old nor standard paths exist initially for isolation
129+
// (vol.reset() in beforeEach should handle this)
130+
131+
// Instantiate LOCALLY for this specific test
132+
const localDocService = new DocumentManagementService();
133+
134+
// Verify DocumentStore was called with the env var path
135+
expect(vi.mocked(DocumentStore)).toHaveBeenCalledWith(expectedEnvDbPath);
136+
// Verify the env var directory was created in memfs
137+
expect(vol.existsSync(mockEnvStorePath)).toBe(true);
138+
// Verify other paths were NOT created (optional but good check)
139+
expect(vol.existsSync(path.dirname(expectedOldDbPath))).toBe(false);
140+
expect(vol.existsSync(path.dirname(expectedStandardDbPath))).toBe(false);
141+
// Verify envPaths was NOT called
142+
expect(mockEnvPathsFn).not.toHaveBeenCalled();
143+
// Verify fs.existsSync was NOT called for the old path check
144+
// (We need to spy on fs.existsSync for this) - Let's skip this assertion for now as it requires more mock setup
145+
} finally {
146+
// Restore original env var value
147+
process.env.DOCS_MCP_STORE_PATH = originalEnvValue;
148+
}
149+
});
128150
});
129151
// --- END: Constructor Path Logic Tests ---
130152

0 commit comments

Comments
 (0)