Skip to content

Commit fd64f3b

Browse files
author
Jordan Munch O'Hare
committed
Initial public release: MCP SSH Unraid v1.0.0
0 parents  commit fd64f3b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+18383
-0
lines changed

.claude/CLAUDE.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# MCP SSH Unraid - Project Instructions
2+
3+
## User Expectations
4+
5+
### Accuracy and Verification
6+
- **Always count before claiming**: Never state tool counts or numbers without actually counting them first (e.g., use `grep -c` to count server.tool() calls)
7+
- **Be precise and consistent**: If documentation says "85 tools" and README says "86 tools", count the actual number and fix all inconsistencies
8+
- **Verify arithmetic**: When removing N tools from X total, actually verify the result instead of guessing
9+
10+
### Docker Registry Push
11+
- **Retry on failure**: If `docker push` fails due to network timeout, retry when requested - the issue may be temporary
12+
- **Don't give up early**: Network issues can be transient, so attempt retries as requested
13+
14+
### Removing Features
15+
When removing tools or features:
16+
1. Count the actual number of tools being removed
17+
2. Count the current total across all files
18+
3. Calculate the new total: current - removed = new
19+
4. Update ALL documentation consistently:
20+
- `.claude/CLAUDE.md` - tool count
21+
- `README.md` - tool count
22+
- Test files - expected counts
23+
- Tool registration tests
24+
25+
## Version Bumping Workflow
26+
27+
After bumping the version in this project, you MUST update the deployment configuration:
28+
29+
1. **Update version in these files:**
30+
- `package.json` - version field
31+
- `src/http-server.ts` - McpServer version (line ~123)
32+
- `src/http-server.ts` - health endpoint version (line ~313)
33+
- `src/__tests__/http-server.test.ts` - expected version in test
34+
35+
2. **Build and test:**
36+
- Run `npm run build && npm test`
37+
- Verify all 403 tests pass
38+
39+
3. **Build and publish Docker image:**
40+
- Build with both version tag and latest:
41+
```bash
42+
docker build -f Dockerfile.http -t your-registry.com/your-org/mcp-ssh-unraid:{version} -t your-registry.com/your-org/mcp-ssh-unraid:latest .
43+
```
44+
- Push both tags to registry:
45+
```bash
46+
docker push your-registry.com/your-org/mcp-ssh-unraid:{version}
47+
docker push your-registry.com/your-org/mcp-ssh-unraid:latest
48+
```
49+
- If push fails with network timeout, retry as requested
50+
51+
4. **Update deployment configuration:**
52+
- Update your deployment config file (e.g., compose.yaml or kubernetes manifest)
53+
- Update the `image:` tag to match the new version
54+
55+
## Testing MCP Functionality
56+
57+
When testing the deployed MCP server, use the MCP tools (e.g., `mcp__unraid-ssh__*`) to verify functionality. Test various filter combinations to ensure the filtering system works correctly.
58+
59+
## Filter System
60+
61+
All 82 tools support comprehensive output filtering. Filters are defined in `src/filters.ts` and applied via `...outputFiltersSchema.shape` pattern. When adding new tools, always include filter support.

.dockerignore

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Dependencies
2+
node_modules
3+
npm-debug.log*
4+
yarn-error.log*
5+
6+
# Build output
7+
dist
8+
9+
# Environment files
10+
.env
11+
.env.local
12+
.env.*.local
13+
14+
# Development files
15+
.devbox
16+
devbox.json
17+
devbox.lock
18+
.envrc
19+
20+
# Version control
21+
.git
22+
.gitignore
23+
.jj
24+
25+
# IDE and editor files
26+
.vscode
27+
.idea
28+
*.swp
29+
*.swo
30+
*~
31+
32+
# Documentation
33+
*.md
34+
!README.md
35+
36+
# Testing
37+
**/__tests__
38+
*.test.ts
39+
*.spec.ts
40+
vitest.config.ts
41+
coverage
42+
43+
# Claude Code
44+
.claude
45+
46+
# OS files
47+
.DS_Store
48+
Thumbs.db
49+
50+
# Logs
51+
logs
52+
*.log

.env.example

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# SSH Connection Configuration
2+
3+
# When running ON the Unraid server itself (in Docker):
4+
# - Use SSH_HOST=localhost or SSH_HOST=127.0.0.1
5+
# - Use network_mode: host in docker-compose.yml
6+
# - SSH_PRIVATE_KEY_PATH should be the path INSIDE the container (leave as is)
7+
# - Set SSH_KEY_HOST_PATH in docker-compose or .env for the host path
8+
9+
# When running remotely (not on Unraid):
10+
# - Use SSH_HOST=unraid.local or your Unraid server's IP/hostname
11+
# - Comment out network_mode: host in docker-compose.yml
12+
13+
# SSH server address (use 'localhost' when running on Unraid itself)
14+
SSH_HOST=localhost
15+
16+
# SSH port
17+
SSH_PORT=22
18+
19+
# SSH username (should be a dedicated read-only user)
20+
SSH_USERNAME=mcp-readonly
21+
22+
# Path to SSH private key INSIDE the container (don't change this)
23+
SSH_PRIVATE_KEY_PATH=/home/mcp/.ssh/id_rsa
24+
25+
# Path to SSH private key on the HOST (for docker-compose volume mount)
26+
# For Unraid: /root/.ssh/id_rsa_mcp or /boot/config/ssh/id_rsa_mcp
27+
# For other systems: /home/user/.ssh/id_rsa_mcp
28+
SSH_KEY_HOST_PATH=/root/.ssh/id_rsa_mcp
29+
30+
# Command execution timeout in milliseconds (default: 15000 = 15 seconds)
31+
# Increase for long-running commands like database dumps
32+
COMMAND_TIMEOUT_MS=15000
33+
34+
# Maximum consecutive command failures before circuit breaker opens (default: 3)
35+
# When circuit breaker is open, commands will fail immediately to prevent retry loops
36+
MAX_CONSECUTIVE_FAILURES=3

.envrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
# Automatically sets up your devbox environment whenever you cd into this
4+
# directory via our direnv integration:
5+
6+
eval "$(devbox generate direnv --print-envrc)"
7+
8+
# check out https://www.jetify.com/docs/devbox/ide_configuration/direnv/
9+
# for more details
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Build and Push Docker Image
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
9+
env:
10+
REGISTRY: ghcr.io
11+
IMAGE_NAME: ${{ github.repository }}
12+
13+
jobs:
14+
build-and-push:
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: read
18+
packages: write
19+
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Docker Buildx
25+
uses: docker/setup-buildx-action@v3
26+
27+
- name: Log in to GitHub Container Registry
28+
uses: docker/login-action@v3
29+
with:
30+
registry: ${{ env.REGISTRY }}
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Extract metadata (tags, labels)
35+
id: meta
36+
uses: docker/metadata-action@v5
37+
with:
38+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
39+
tags: |
40+
type=semver,pattern={{version}}
41+
type=semver,pattern={{major}}.{{minor}}
42+
type=semver,pattern={{major}}
43+
type=raw,value=latest
44+
45+
- name: Build and push Docker image
46+
uses: docker/build-push-action@v5
47+
with:
48+
context: .
49+
file: ./Dockerfile
50+
push: true
51+
tags: ${{ steps.meta.outputs.tags }}
52+
labels: ${{ steps.meta.outputs.labels }}
53+
cache-from: type=gha
54+
cache-to: type=gha,mode=max
55+
platforms: linux/amd64,linux/arm64
56+
57+
- name: Image digest
58+
run: echo "Image pushed with digest ${{ steps.meta.outputs.digest }}"

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build output
5+
dist/
6+
7+
# Environment variables
8+
.env
9+
10+
# Local MCP configuration
11+
mcp-config.json
12+
13+
# Logs
14+
*.log
15+
logs/
16+
17+
# Editor directories
18+
.vscode/
19+
.idea/
20+
21+
# OS files
22+
.DS_Store
23+
Thumbs.db

.serena/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/cache

.serena/project.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# list of languages for which language servers are started; choose from:
2+
# al bash clojure cpp csharp csharp_omnisharp
3+
# dart elixir elm erlang fortran go
4+
# haskell java julia kotlin lua markdown
5+
# nix perl php python python_jedi r
6+
# rego ruby ruby_solargraph rust scala swift
7+
# terraform typescript typescript_vts yaml zig
8+
# Note:
9+
# - For C, use cpp
10+
# - For JavaScript, use typescript
11+
# Special requirements:
12+
# - csharp: Requires the presence of a .sln file in the project folder.
13+
# When using multiple languages, the first language server that supports a given file will be used for that file.
14+
# The first language is the default language and the respective language server will be used as a fallback.
15+
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
16+
languages:
17+
- typescript
18+
19+
# the encoding used by text files in the project
20+
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
21+
encoding: "utf-8"
22+
23+
# whether to use the project's gitignore file to ignore files
24+
# Added on 2025-04-07
25+
ignore_all_files_in_gitignore: true
26+
27+
# list of additional paths to ignore
28+
# same syntax as gitignore, so you can use * and **
29+
# Was previously called `ignored_dirs`, please update your config if you are using that.
30+
# Added (renamed) on 2025-04-07
31+
ignored_paths: []
32+
33+
# whether the project is in read-only mode
34+
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
35+
# Added on 2025-04-18
36+
read_only: false
37+
38+
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
39+
# Below is the complete list of tools for convenience.
40+
# To make sure you have the latest list of tools, and to view their descriptions,
41+
# execute `uv run scripts/print_tool_overview.py`.
42+
#
43+
# * `activate_project`: Activates a project by name.
44+
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
45+
# * `create_text_file`: Creates/overwrites a file in the project directory.
46+
# * `delete_lines`: Deletes a range of lines within a file.
47+
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
48+
# * `execute_shell_command`: Executes a shell command.
49+
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
50+
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
51+
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
52+
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
53+
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
54+
# * `initial_instructions`: Gets the initial instructions for the current project.
55+
# Should only be used in settings where the system prompt cannot be set,
56+
# e.g. in clients you have no control over, like Claude Desktop.
57+
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
58+
# * `insert_at_line`: Inserts content at a given line in a file.
59+
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
60+
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
61+
# * `list_memories`: Lists memories in Serena's project-specific memory store.
62+
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
63+
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
64+
# * `read_file`: Reads a file within the project directory.
65+
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
66+
# * `remove_project`: Removes a project from the Serena configuration.
67+
# * `replace_lines`: Replaces a range of lines within a file with new content.
68+
# * `replace_symbol_body`: Replaces the full definition of a symbol.
69+
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
70+
# * `search_for_pattern`: Performs a search for a pattern in the project.
71+
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
72+
# * `switch_modes`: Activates modes by providing a list of their names
73+
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
74+
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
75+
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
76+
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
77+
excluded_tools: []
78+
79+
# initial prompt for the project. It will always be given to the LLM upon activating the project
80+
# (contrary to the memories, which are loaded on demand).
81+
initial_prompt: ""
82+
83+
project_name: "mcp-ssh-unraid"
84+
included_optional_tools: []

Dockerfile

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Multi-stage build for optimal image size
2+
FROM node:20-alpine AS builder
3+
4+
WORKDIR /app
5+
6+
# Copy package files
7+
COPY package*.json ./
8+
9+
# Install all dependencies (including devDependencies for building)
10+
RUN npm ci
11+
12+
# Copy source code and config
13+
COPY tsconfig.json ./
14+
COPY src ./src
15+
16+
# Build TypeScript
17+
RUN npm run build
18+
19+
# Production stage
20+
FROM node:20-alpine
21+
22+
WORKDIR /app
23+
24+
# Create non-root user for security
25+
RUN addgroup -g 1001 -S mcp && \
26+
adduser -u 1001 -S mcp -G mcp
27+
28+
# Copy package files
29+
COPY package*.json ./
30+
31+
# Install only production dependencies
32+
RUN npm ci --only=production && \
33+
npm cache clean --force
34+
35+
# Copy built application from builder
36+
COPY --from=builder /app/dist ./dist
37+
38+
# Create directory for SSH keys
39+
RUN mkdir -p /home/mcp/.ssh && \
40+
chown -R mcp:mcp /home/mcp/.ssh && \
41+
chmod 700 /home/mcp/.ssh
42+
43+
# Switch to non-root user
44+
USER mcp
45+
46+
# Set environment for production
47+
ENV NODE_ENV=production
48+
49+
# Health check to verify SSH connectivity
50+
# This will check if the SSH connection can be established
51+
# Runs every 30 seconds, timeout after 10 seconds
52+
# Note: Requires SSH_HOST, SSH_USERNAME, and SSH_PRIVATE_KEY_PATH env vars to be set
53+
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
54+
CMD node -e "const {NodeSSH}=require('node-ssh');const ssh=new NodeSSH();ssh.connect({host:process.env.SSH_HOST||'localhost',port:parseInt(process.env.SSH_PORT||'22'),username:process.env.SSH_USERNAME,privateKeyPath:process.env.SSH_PRIVATE_KEY_PATH}).then(()=>{ssh.dispose();process.exit(0)}).catch(()=>process.exit(1))" || exit 1
55+
56+
# Run the HTTP/SSE MCP server
57+
CMD ["node", "dist/http-server.js"]

0 commit comments

Comments
 (0)