diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index bbca418..b3f99e1 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -1,7 +1,8 @@
{
"permissions": {
"allow": [
- "Bash(chmod:*)"
+ "Bash(chmod:*)",
+ "Bash(cargo build:*)"
],
"deny": []
}
diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md
new file mode 100644
index 0000000..6877b31
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/release.md
@@ -0,0 +1,58 @@
+---
+name: Release Checklist
+about: Checklist for preparing a new release
+title: 'Release v[VERSION]'
+labels: 'release'
+assignees: ''
+
+---
+
+## Pre-release Checklist
+
+- [ ] All planned features and fixes are merged
+- [ ] All tests are passing on main branch
+- [ ] Documentation is updated
+- [ ] CHANGELOG.md is updated (if maintained separately)
+- [ ] Version is updated in Cargo.toml
+- [ ] No critical security vulnerabilities in dependencies
+
+## Release Process
+
+- [ ] Run `./scripts/release.sh [patch|minor|major|version X.Y.Z]`
+- [ ] Verify all CI checks pass
+- [ ] Tag is created and pushed
+- [ ] GitHub release is created automatically
+- [ ] Binaries are built for all platforms
+- [ ] Crate is published to crates.io (for stable releases)
+- [ ] Docker images are pushed
+
+## Post-release Tasks
+
+- [ ] Verify release artifacts are available
+- [ ] Test installation from released binaries
+- [ ] Update any dependent projects
+- [ ] Announce release (if applicable)
+
+## Release Notes
+
+
+
+## Verification Commands
+
+```bash
+# Test the release script (dry run)
+./scripts/release.sh patch --dry-run
+
+# Run pre-release checks
+./scripts/release.sh check
+
+# Create the actual release
+./scripts/release.sh patch # or minor/major/version X.Y.Z
+```
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..e5cc9df
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,490 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*.*.*'
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version to release (e.g., v1.0.0)'
+ required: true
+ type: string
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ validate-release:
+ name: Validate Release
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.get-version.outputs.version }}
+ tag: ${{ steps.get-version.outputs.tag }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Get version from tag or input
+ id: get-version
+ run: |
+ if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
+ VERSION="${{ github.event.inputs.version }}"
+ echo "version=${VERSION#v}" >> $GITHUB_OUTPUT
+ echo "tag=${VERSION}" >> $GITHUB_OUTPUT
+ else
+ VERSION="${GITHUB_REF#refs/tags/}"
+ echo "version=${VERSION#v}" >> $GITHUB_OUTPUT
+ echo "tag=${VERSION}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Validate version format
+ run: |
+ VERSION="${{ steps.get-version.outputs.version }}"
+ if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
+ echo "â Invalid version format: $VERSION"
+ echo "Expected format: X.Y.Z or X.Y.Z-suffix"
+ exit 1
+ fi
+ echo "â
Valid version format: $VERSION"
+
+ - name: Check if version matches Cargo.toml
+ run: |
+ CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
+ INPUT_VERSION="${{ steps.get-version.outputs.version }}"
+ if [ "$CARGO_VERSION" != "$INPUT_VERSION" ]; then
+ echo "â Version mismatch:"
+ echo " Cargo.toml: $CARGO_VERSION"
+ echo " Tag/Input: $INPUT_VERSION"
+ exit 1
+ fi
+ echo "â
Version matches Cargo.toml: $INPUT_VERSION"
+
+ test:
+ name: Run Tests
+ runs-on: ${{ matrix.os }}
+ needs: validate-release
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ rust: [stable]
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ matrix.rust }}
+
+ - name: Cache Cargo registry
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Install system dependencies (Ubuntu)
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ pkg-config \
+ libssl-dev \
+ libfontconfig1-dev \
+ libfreetype6-dev \
+ libjpeg-dev \
+ libpng-dev
+
+ - name: Install system dependencies (macOS)
+ if: matrix.os == 'macos-latest'
+ run: |
+ brew update
+ brew install pkg-config freetype jpeg libpng
+
+ - name: Run tests
+ run: cargo test --all-features --verbose
+
+ build:
+ name: Build Release Artifacts
+ runs-on: ${{ matrix.job.os }}
+ needs: [validate-release, test]
+ strategy:
+ matrix:
+ job:
+ - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, use-cross: false }
+ - { os: ubuntu-latest, target: x86_64-unknown-linux-musl, use-cross: true }
+ - { os: ubuntu-latest, target: aarch64-unknown-linux-gnu, use-cross: true }
+ - { os: ubuntu-latest, target: aarch64-unknown-linux-musl, use-cross: true }
+ - { os: windows-latest, target: x86_64-pc-windows-msvc, use-cross: false }
+ - { os: macos-latest, target: x86_64-apple-darwin, use-cross: false }
+ - { os: macos-latest, target: aarch64-apple-darwin, use-cross: false }
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.job.target }}
+
+ - name: Install cross compilation tools
+ if: matrix.job.use-cross
+ run: |
+ cargo install cross --git https://github.com/cross-rs/cross
+
+ - name: Cache Cargo registry
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: ${{ runner.os }}-${{ matrix.job.target }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Install system dependencies (Ubuntu)
+ if: matrix.job.os == 'ubuntu-latest' && !matrix.job.use-cross
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ pkg-config \
+ libssl-dev \
+ libfontconfig1-dev \
+ libfreetype6-dev \
+ libjpeg-dev \
+ libpng-dev
+
+ - name: Install system dependencies (macOS)
+ if: matrix.job.os == 'macos-latest'
+ run: |
+ brew update
+ brew install pkg-config freetype jpeg libpng
+
+ - name: Build release binary
+ run: |
+ if [ "${{ matrix.job.use-cross }}" = "true" ]; then
+ cross build --release --target ${{ matrix.job.target }} --all-features
+ else
+ cargo build --release --target ${{ matrix.job.target }} --all-features
+ fi
+
+ - name: Prepare release archive
+ shell: bash
+ run: |
+ VERSION="${{ needs.validate-release.outputs.version }}"
+ TARGET="${{ matrix.job.target }}"
+
+ # Create staging directory
+ mkdir -p staging
+
+ # Copy binary
+ if [[ "${{ matrix.job.os }}" == "windows-latest" ]]; then
+ cp "target/${TARGET}/release/docx-mcp.exe" staging/
+ BINARY="docx-mcp.exe"
+ else
+ cp "target/${TARGET}/release/docx-mcp" staging/
+ BINARY="docx-mcp"
+ fi
+
+ # Copy additional files
+ cp README.md staging/
+ cp LICENSE staging/
+
+ # Create archive name
+ ARCHIVE="docx-mcp-${VERSION}-${TARGET}"
+
+ # Create archive
+ cd staging
+ if [[ "${{ matrix.job.os }}" == "windows-latest" ]]; then
+ 7z a "../${ARCHIVE}.zip" *
+ echo "ARCHIVE=${ARCHIVE}.zip" >> $GITHUB_ENV
+ else
+ tar czf "../${ARCHIVE}.tar.gz" *
+ echo "ARCHIVE=${ARCHIVE}.tar.gz" >> $GITHUB_ENV
+ fi
+ cd ..
+
+ # Generate checksums
+ if [[ "${{ matrix.job.os }}" == "windows-latest" ]]; then
+ certutil -hashfile "${ARCHIVE}.zip" SHA256 > "${ARCHIVE}.zip.sha256"
+ else
+ shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
+ fi
+
+ echo "BINARY=${BINARY}" >> $GITHUB_ENV
+
+ - name: Upload release artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: release-${{ matrix.job.target }}
+ path: |
+ ${{ env.ARCHIVE }}
+ ${{ env.ARCHIVE }}.sha256
+
+ create-release:
+ name: Create GitHub Release
+ runs-on: ubuntu-latest
+ needs: [validate-release, build]
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: artifacts
+
+ - name: Prepare release assets
+ run: |
+ mkdir -p release-assets
+ find artifacts -type f -name "*.tar.gz" -o -name "*.zip" -o -name "*.sha256" | \
+ xargs -I {} cp {} release-assets/
+ ls -la release-assets/
+
+ - name: Generate changelog
+ id: changelog
+ run: |
+ VERSION="${{ needs.validate-release.outputs.version }}"
+ TAG="${{ needs.validate-release.outputs.tag }}"
+
+ # Get previous tag
+ PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${TAG}$" | head -1)
+
+ echo "Generating changelog from ${PREV_TAG} to ${TAG}"
+
+ # Generate changelog
+ if [ -n "$PREV_TAG" ]; then
+ CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges ${PREV_TAG}..HEAD)
+ else
+ CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges)
+ fi
+
+ # Create release notes
+ cat > release-notes.md << EOF
+ ## What's Changed
+
+ ${CHANGELOG}
+
+ ## Installation
+
+ ### Pre-built Binaries
+
+ Download the appropriate binary for your system:
+
+ - **Linux x86_64**: \`docx-mcp-${VERSION}-x86_64-unknown-linux-gnu.tar.gz\`
+ - **Linux x86_64 (musl)**: \`docx-mcp-${VERSION}-x86_64-unknown-linux-musl.tar.gz\`
+ - **Linux ARM64**: \`docx-mcp-${VERSION}-aarch64-unknown-linux-gnu.tar.gz\`
+ - **macOS Intel**: \`docx-mcp-${VERSION}-x86_64-apple-darwin.tar.gz\`
+ - **macOS Apple Silicon**: \`docx-mcp-${VERSION}-aarch64-apple-darwin.tar.gz\`
+ - **Windows x86_64**: \`docx-mcp-${VERSION}-x86_64-pc-windows-msvc.zip\`
+
+ ### From Source
+
+ \`\`\`bash
+ cargo install --git https://github.com/hongkongkiwi/docx-mcp --tag ${TAG}
+ \`\`\`
+
+ ### Verification
+
+ All binaries are provided with SHA256 checksums for verification:
+
+ \`\`\`bash
+ # Linux/macOS
+ shasum -a 256 -c docx-mcp-${VERSION}-your-target.tar.gz.sha256
+
+ # Windows
+ certutil -hashfile docx-mcp-${VERSION}-x86_64-pc-windows-msvc.zip SHA256
+ \`\`\`
+
+ ## Full Changelog
+
+ **Full Changelog**: https://github.com/hongkongkiwi/docx-mcp/compare/${PREV_TAG}...${TAG}
+ EOF
+
+ echo "CHANGELOG_FILE=release-notes.md" >> $GITHUB_OUTPUT
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ needs.validate-release.outputs.tag }}
+ name: Release ${{ needs.validate-release.outputs.tag }}
+ body_path: ${{ steps.changelog.outputs.CHANGELOG_FILE }}
+ files: release-assets/*
+ draft: false
+ prerelease: ${{ contains(needs.validate-release.outputs.version, '-') }}
+ generate_release_notes: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ publish-crate:
+ name: Publish to crates.io
+ runs-on: ubuntu-latest
+ needs: [validate-release, create-release]
+ if: ${{ !contains(needs.validate-release.outputs.version, '-') }} # Only publish stable releases
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install system dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ pkg-config \
+ libssl-dev \
+ libfontconfig1-dev \
+ libfreetype6-dev \
+ libjpeg-dev \
+ libpng-dev
+
+ - name: Cache Cargo registry
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: ubuntu-cargo-publish-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Verify package
+ run: cargo package --dry-run
+
+ - name: Publish to crates.io
+ run: cargo publish
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+
+ docker-release:
+ name: Build and Push Docker Images
+ runs-on: ubuntu-latest
+ needs: [validate-release, create-release]
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Login to Docker Hub
+ if: ${{ secrets.DOCKERHUB_USERNAME && secrets.DOCKERHUB_TOKEN }}
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: |
+ ghcr.io/${{ github.repository }}
+ ${{ secrets.DOCKERHUB_USERNAME && format('{0}/docx-mcp', secrets.DOCKERHUB_USERNAME) || '' }}
+ tags: |
+ type=ref,event=tag
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ update-docs:
+ name: Update Documentation
+ runs-on: ubuntu-latest
+ needs: [validate-release, create-release]
+ permissions:
+ contents: write
+ pages: write
+ id-token: write
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install system dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ build-essential \
+ pkg-config \
+ libssl-dev \
+ libfontconfig1-dev \
+ libfreetype6-dev \
+ libjpeg-dev \
+ libpng-dev
+
+ - name: Generate documentation
+ run: |
+ cargo doc --all-features --no-deps
+ echo '' > target/doc/index.html
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./target/doc
+ cname: docs.example.com # Replace with your custom domain if you have one
+
+ notify-success:
+ name: Notify Release Success
+ runs-on: ubuntu-latest
+ needs: [create-release, publish-crate, docker-release, update-docs]
+ if: success()
+ steps:
+ - name: Success notification
+ run: |
+ echo "đ Release ${{ needs.validate-release.outputs.tag }} completed successfully!"
+ echo "- â
GitHub release created"
+ echo "- â
Binaries built for all platforms"
+ echo "- â
Published to crates.io"
+ echo "- â
Docker images pushed"
+ echo "- â
Documentation updated"
+
+ notify-failure:
+ name: Notify Release Failure
+ runs-on: ubuntu-latest
+ needs: [validate-release, test, build, create-release, publish-crate, docker-release, update-docs]
+ if: failure()
+ steps:
+ - name: Failure notification
+ run: |
+ echo "â Release ${{ needs.validate-release.outputs.tag }} failed!"
+ echo "Please check the workflow logs for details."
+ exit 1
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 528d77f..f4936c4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,11 +2,28 @@
name = "docx-mcp"
version = "0.1.0"
edition = "2021"
+authors = ["Your Name "]
+description = "A comprehensive Model Context Protocol (MCP) server for Microsoft Word DOCX file manipulation"
+documentation = "https://docs.rs/docx-mcp"
+homepage = "https://github.com/hongkongkiwi/docx-mcp"
+repository = "https://github.com/hongkongkiwi/docx-mcp"
+readme = "README.md"
+license = "MIT"
+keywords = ["mcp", "docx", "word", "document", "pdf"]
+categories = ["text-processing", "api-bindings", "command-line-utilities"]
+exclude = [
+ "/.github/*",
+ "/tests/fixtures/*",
+ "/example/*",
+ "/benches/*",
+ "/.gitignore",
+ "/deny.toml"
+]
[dependencies]
# Official MCP SDK
-mcp-server = "0.3"
-mcp-core = "0.3"
+mcp-server = "0.1"
+mcp-core = "0.1"
# Async runtime
tokio = { version = "1.40", features = ["full"] }
@@ -26,7 +43,7 @@ lopdf = "0.34"
rusttype = "0.9" # Font rendering in pure Rust
# Embedded fonts for PDF
-include_bytes_plus = "1.0"
+include-bytes-plus = "1.0"
# Image processing (pure Rust)
image = { version = "0.25", features = ["png", "jpeg", "webp", "bmp", "gif"] }
@@ -66,6 +83,9 @@ chrono = { version = "0.4", features = ["serde"] }
regex = "1.10"
once_cell = "1.20"
+# Command line argument parsing
+clap = { version = "4.5", features = ["derive"] }
+
# Optional external tool support
headless_chrome = { version = "1.0", optional = true }
wkhtmltopdf = { version = "0.4", optional = true }
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b84d69b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,77 @@
+# Multi-stage Docker build for docx-mcp
+FROM rust:1.75-slim as builder
+
+# Install system dependencies for building
+RUN apt-get update && apt-get install -y \
+ pkg-config \
+ libssl-dev \
+ libfontconfig1-dev \
+ libfreetype6-dev \
+ libjpeg-dev \
+ libpng-dev \
+ build-essential \
+ && rm -rf /var/lib/apt/lists/*
+
+# Set working directory
+WORKDIR /app
+
+# Copy manifests
+COPY Cargo.toml Cargo.lock ./
+COPY build.rs ./
+
+# Copy source code
+COPY src/ ./src/
+COPY benches/ ./benches/
+COPY tests/ ./tests/
+
+# Build the application
+RUN cargo build --release --all-features
+
+# Runtime stage
+FROM debian:bookworm-slim
+
+# Install runtime dependencies
+RUN apt-get update && apt-get install -y \
+ libssl3 \
+ libfontconfig1 \
+ libfreetype6 \
+ libjpeg62-turbo \
+ libpng16-16 \
+ ca-certificates \
+ libreoffice \
+ poppler-utils \
+ && rm -rf /var/lib/apt/lists/*
+
+# Create non-root user
+RUN groupadd -r docxmcp && useradd -r -g docxmcp -s /bin/bash -d /app docxmcp
+
+# Create app directory and set ownership
+WORKDIR /app
+RUN chown -R docxmcp:docxmcp /app
+
+# Copy the built binary from builder stage
+COPY --from=builder /app/target/release/docx-mcp /usr/local/bin/docx-mcp
+RUN chmod +x /usr/local/bin/docx-mcp
+
+# Copy additional files if needed
+COPY README.md LICENSE ./
+
+# Switch to non-root user
+USER docxmcp
+
+# Create temp directory for document processing
+RUN mkdir -p /tmp/docx-mcp && chmod 755 /tmp/docx-mcp
+
+# Expose default MCP port (though MCP typically uses stdin/stdout)
+EXPOSE 8080
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD /usr/local/bin/docx-mcp --version || exit 1
+
+# Set environment variables
+ENV RUST_LOG=info
+ENV DOCX_MCP_TEMP_DIR=/tmp/docx-mcp
+
+# Default command
+CMD ["/usr/local/bin/docx-mcp"]
\ No newline at end of file
diff --git a/README.md b/README.md
index a5de9ad..961fb14 100644
--- a/README.md
+++ b/README.md
@@ -60,8 +60,13 @@ The server includes comprehensive security features for enterprise and restricte
### Readonly Mode
```bash
# Enable readonly mode - only allows document viewing and analysis
+
+# Using environment variables
export DOCX_MCP_READONLY=true
./target/release/docx-mcp
+
+# Using command line arguments
+./target/release/docx-mcp --readonly
```
In readonly mode, only these operations are allowed:
@@ -74,32 +79,51 @@ In readonly mode, only these operations are allowed:
### Command Filtering
```bash
# Whitelist specific commands only
+
+# Using environment variables
export DOCX_MCP_WHITELIST="open_document,extract_text,get_metadata,export_to_markdown"
+# Using command line arguments
+./target/release/docx-mcp --whitelist open_document,extract_text,get_metadata,export_to_markdown
+
# Or blacklist dangerous commands
+
+# Using environment variables
export DOCX_MCP_BLACKLIST="save_document,convert_to_pdf,merge_documents"
+
+# Using command line arguments
+./target/release/docx-mcp --blacklist save_document,convert_to_pdf,merge_documents
```
### Sandbox Mode
```bash
# Restrict all file operations to temp directory only
+
+# Using environment variables
export DOCX_MCP_SANDBOX=true
./target/release/docx-mcp
+
+# Using command line arguments
+./target/release/docx-mcp --sandbox
```
### Resource Limits
```bash
# Set maximum document size (100MB default)
+
+# Using environment variables
export DOCX_MCP_MAX_SIZE=52428800 # 50MB
-
-# Set maximum number of open documents
export DOCX_MCP_MAX_DOCS=20
-
-# Disable external tools
export DOCX_MCP_NO_EXTERNAL_TOOLS=true
-
-# Disable network operations
export DOCX_MCP_NO_NETWORK=true
+./target/release/docx-mcp
+
+# Using command line arguments
+./target/release/docx-mcp \
+ --max-size 52428800 \
+ --max-docs 20 \
+ --no-external-tools \
+ --no-network
```
## đ¤ AI Tool Integration
@@ -125,6 +149,39 @@ Add to your Claude Desktop configuration file:
}
```
+**With Security Options (using command-line arguments):**
+```json
+{
+ "mcpServers": {
+ "docx": {
+ "command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
+ "args": ["--readonly", "--max-size", "52428800", "--no-network"],
+ "env": {
+ "RUST_LOG": "info"
+ }
+ }
+ }
+}
+```
+
+**With Security Options (using environment variables):**
+```json
+{
+ "mcpServers": {
+ "docx": {
+ "command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
+ "args": [],
+ "env": {
+ "RUST_LOG": "info",
+ "DOCX_MCP_READONLY": "true",
+ "DOCX_MCP_MAX_SIZE": "52428800",
+ "DOCX_MCP_NO_NETWORK": "true"
+ }
+ }
+ }
+}
+```
+
After adding, restart Claude Desktop. You can then ask Claude to:
- "Create a new Word document with our Q4 report"
- "Convert this DOCX file to PDF"
@@ -135,6 +192,7 @@ After adding, restart Claude Desktop. You can then ask Claude to:
Add to your Cursor settings (`~/.cursor/config.json` or through Settings UI):
+**Basic Configuration:**
```json
{
"mcp": {
@@ -151,10 +209,24 @@ Add to your Cursor settings (`~/.cursor/config.json` or through Settings UI):
}
```
-### Windsurf (Codeium)
-
-Add to your Windsurf configuration (`~/.windsurf/config.json`):
+**With Security Options (using command-line arguments):**
+```json
+{
+ "mcp": {
+ "servers": {
+ "docx": {
+ "command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
+ "args": ["--sandbox", "--whitelist", "open_document,extract_text,export_to_markdown"],
+ "env": {
+ "RUST_LOG": "info"
+ }
+ }
+ }
+ }
+}
+```
+**With Security Options (using environment variables):**
```json
{
"mcp": {
@@ -162,6 +234,46 @@ Add to your Windsurf configuration (`~/.windsurf/config.json`):
"docx": {
"command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
"args": [],
+ "env": {
+ "RUST_LOG": "info",
+ "DOCX_MCP_SANDBOX": "true",
+ "DOCX_MCP_WHITELIST": "open_document,extract_text,export_to_markdown"
+ }
+ }
+ }
+ }
+}
+```
+
+### Windsurf (Codeium)
+
+Add to your Windsurf configuration (`~/.windsurf/config.json`):
+
+**Basic Configuration:**
+```json
+{
+ "mcp": {
+ "servers": {
+ "docx": {
+ "command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
+ "args": [],
+ "env": {
+ "RUST_LOG": "info"
+ }
+ }
+ }
+ }
+}
+```
+
+**With Security Options (using arguments):**
+```json
+{
+ "mcp": {
+ "servers": {
+ "docx": {
+ "command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
+ "args": ["--readonly", "--no-external-tools"],
"env": {
"RUST_LOG": "info"
}
@@ -175,6 +287,7 @@ Add to your Windsurf configuration (`~/.windsurf/config.json`):
Add to your Continue configuration (`~/.continue/config.json`):
+**Basic Configuration:**
```json
{
"models": [
@@ -192,10 +305,29 @@ Add to your Continue configuration (`~/.continue/config.json`):
}
```
+**With Security Options:**
+```json
+{
+ "models": [
+ {
+ "title": "Your Model",
+ "provider": "your-provider",
+ "mcp_servers": {
+ "docx": {
+ "command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
+ "args": ["--sandbox", "--max-size", "10485760"]
+ }
+ }
+ }
+ ]
+}
+```
+
### VS Code with MCP Extension
If using the MCP extension for VS Code, add to your workspace settings (`.vscode/settings.json`):
+**Basic Configuration:**
```json
{
"mcp.servers": {
@@ -210,6 +342,67 @@ If using the MCP extension for VS Code, add to your workspace settings (`.vscode
}
```
+**With Security Options:**
+```json
+{
+ "mcp.servers": {
+ "docx": {
+ "command": "/absolute/path/to/docx-mcp/target/release/docx-mcp",
+ "args": ["--readonly", "--blacklist", "save_document,merge_documents"],
+ "env": {
+ "RUST_LOG": "info"
+ }
+ }
+ }
+}
+```
+
+## đ§ Command Line Arguments
+
+The DOCX MCP server supports the following command-line arguments for configuration:
+
+```bash
+docx-mcp --help
+```
+
+### Available Arguments
+
+| Argument | Environment Variable | Description | Example |
+|----------|---------------------|-------------|---------|
+| `--readonly` | `DOCX_MCP_READONLY=true` | Enable readonly mode - only viewing operations | `--readonly` |
+| `--whitelist ` | `DOCX_MCP_WHITELIST` | Comma-separated list of allowed commands | `--whitelist open_document,extract_text` |
+| `--blacklist ` | `DOCX_MCP_BLACKLIST` | Comma-separated list of forbidden commands | `--blacklist save_document,convert_to_pdf` |
+| `--sandbox` | `DOCX_MCP_SANDBOX=true` | Restrict file operations to temp directory only | `--sandbox` |
+| `--no-external-tools` | `DOCX_MCP_NO_EXTERNAL_TOOLS=true` | Disable external tools (LibreOffice, etc.) | `--no-external-tools` |
+| `--no-network` | `DOCX_MCP_NO_NETWORK=true` | Disable network operations | `--no-network` |
+| `--max-size ` | `DOCX_MCP_MAX_SIZE` | Maximum document size in bytes | `--max-size 52428800` |
+| `--max-docs ` | `DOCX_MCP_MAX_DOCS` | Maximum number of open documents | `--max-docs 20` |
+| `--help` | - | Show help information | `--help` |
+| `--version` | - | Show version information | `--version` |
+
+### Example Usage
+
+```bash
+# Basic usage
+./target/release/docx-mcp
+
+# Readonly mode with size limit
+./target/release/docx-mcp --readonly --max-size 10485760
+
+# Sandbox mode with command whitelist
+./target/release/docx-mcp --sandbox --whitelist open_document,extract_text,export_to_markdown
+
+# Multiple security options
+./target/release/docx-mcp \
+ --readonly \
+ --no-external-tools \
+ --no-network \
+ --max-size 52428800 \
+ --max-docs 10
+```
+
+**Note:** Command-line arguments take precedence over environment variables when both are specified.
+
## đ Features
### Document Operations
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..1a26e65
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,249 @@
+# Release Guide
+
+This document describes the release process for docx-mcp.
+
+## Overview
+
+The release process is automated using GitHub Actions and includes:
+
+- Automated testing on multiple platforms
+- Building release binaries for all supported targets
+- Publishing to crates.io
+- Creating GitHub releases with binaries
+- Building and pushing Docker images
+- Updating documentation
+
+## Release Types
+
+### Semantic Versioning
+
+We follow [Semantic Versioning](https://semver.org/):
+
+- **MAJOR**: Incompatible API changes
+- **MINOR**: New features (backwards compatible)
+- **PATCH**: Bug fixes (backwards compatible)
+
+### Pre-release Versions
+
+Pre-release versions can include suffixes like:
+- `1.0.0-alpha.1` - Alpha releases
+- `1.0.0-beta.1` - Beta releases
+- `1.0.0-rc.1` - Release candidates
+
+## Quick Release Process
+
+For most releases, use the automated release script:
+
+```bash
+# Patch release (1.0.0 -> 1.0.1)
+./scripts/release.sh patch
+
+# Minor release (1.0.0 -> 1.1.0)
+./scripts/release.sh minor
+
+# Major release (1.0.0 -> 2.0.0)
+./scripts/release.sh major
+
+# Specific version
+./scripts/release.sh version 1.5.0
+
+# Pre-release
+./scripts/release.sh version 1.0.0-beta.1
+```
+
+## Manual Release Process
+
+If you need to create a release manually:
+
+### 1. Pre-release Checks
+
+```bash
+# Run all checks
+./scripts/release.sh check
+
+# Or manually:
+cargo fmt --all -- --check
+cargo clippy --all-targets --all-features -- -D warnings
+cargo test --all-features
+cargo build --release --all-features
+cargo package --dry-run
+```
+
+### 2. Update Version
+
+Update the version in `Cargo.toml`:
+
+```toml
+[package]
+version = "1.2.3"
+```
+
+Update `Cargo.lock`:
+
+```bash
+cargo update -p docx-mcp
+```
+
+### 3. Commit and Tag
+
+```bash
+git add Cargo.toml Cargo.lock
+git commit -m "Release v1.2.3"
+git tag -a "v1.2.3" -m "Release v1.2.3"
+git push origin main
+git push origin v1.2.3
+```
+
+### 4. GitHub Actions
+
+The release workflow will automatically:
+
+1. Validate the release
+2. Run tests on all platforms
+3. Build binaries for all targets
+4. Create GitHub release
+5. Publish to crates.io (stable releases only)
+6. Build and push Docker images
+7. Update documentation
+
+## Supported Platforms
+
+Release binaries are built for:
+
+- **Linux**: x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
+- **Linux ARM**: aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl
+- **macOS**: x86_64-apple-darwin, aarch64-apple-darwin
+- **Windows**: x86_64-pc-windows-msvc
+
+## Docker Images
+
+Docker images are published to:
+
+- GitHub Container Registry: `ghcr.io/hongkongkiwi/docx-mcp`
+- Docker Hub: `dockerhub-username/docx-mcp` (if configured)
+
+Tags include:
+- `latest` - Latest stable release
+- `v1.2.3` - Specific version
+- `1.2.3` - Semantic version
+- `1.2` - Major.minor version
+- `1` - Major version
+
+## Publishing to crates.io
+
+Stable releases (without pre-release suffixes) are automatically published to crates.io.
+
+### Prerequisites
+
+1. Set `CARGO_REGISTRY_TOKEN` secret in GitHub repository settings
+2. Ensure you have publishing permissions for the crate
+
+### Manual Publishing
+
+```bash
+# Dry run
+cargo publish --dry-run
+
+# Publish
+cargo publish
+```
+
+## Troubleshooting
+
+### Release Workflow Fails
+
+1. Check the Actions tab in GitHub for detailed logs
+2. Common issues:
+ - Version mismatch between tag and Cargo.toml
+ - Tests failing on specific platforms
+ - Missing secrets (CARGO_REGISTRY_TOKEN, DOCKERHUB credentials)
+
+### Version Already Exists
+
+If you need to recreate a release:
+
+1. Delete the tag: `git tag -d v1.2.3 && git push origin :v1.2.3`
+2. Delete the GitHub release (if created)
+3. Create the tag again
+
+### Docker Build Fails
+
+1. Check if all dependencies are available in the Docker environment
+2. Verify Dockerfile syntax and build context
+3. Test locally: `docker build -t docx-mcp:test .`
+
+### crates.io Publishing Fails
+
+1. Verify `CARGO_REGISTRY_TOKEN` is set and valid
+2. Check if version already exists
+3. Ensure all required metadata is in Cargo.toml
+4. Run `cargo package --dry-run` to check for issues
+
+## Security Considerations
+
+### Signing Releases
+
+Currently, releases are not cryptographically signed. Consider adding:
+
+1. GPG signing of Git tags
+2. Binary signing with platform-specific tools
+3. SBOM (Software Bill of Materials) generation
+
+### Supply Chain Security
+
+- Dependencies are audited in CI with `cargo audit`
+- Docker images use specific base image versions
+- Build reproducibility is enhanced with Rust's deterministic builds
+
+## Release Checklist
+
+Use this checklist for important releases:
+
+- [ ] All planned features are implemented
+- [ ] All tests pass locally and in CI
+- [ ] Documentation is updated
+- [ ] Breaking changes are documented
+- [ ] Migration guide is provided (for major releases)
+- [ ] Security implications are reviewed
+- [ ] Performance regression tests pass
+- [ ] Cross-platform compatibility verified
+- [ ] Release notes are prepared
+
+## Post-Release Tasks
+
+After a release:
+
+1. **Verify Installation**: Test installation from released binaries
+2. **Update Examples**: Update example configurations if needed
+3. **Notify Users**: Announce significant releases
+4. **Monitor Issues**: Watch for issues related to the new release
+5. **Update Dependencies**: Consider updating dependent projects
+
+## Emergency Releases
+
+For critical security fixes:
+
+1. Create a hotfix branch from the affected release tag
+2. Apply minimal fix
+3. Follow expedited release process
+4. Consider yanking affected versions from crates.io if necessary
+
+```bash
+# Yank a version from crates.io (if needed)
+cargo yank --version 1.2.3
+
+# Un-yank if needed later
+cargo yank --version 1.2.3 --undo
+```
+
+## Release Schedule
+
+- **Patch releases**: As needed for bug fixes
+- **Minor releases**: Monthly or when significant features accumulate
+- **Major releases**: Annually or when breaking changes are necessary
+
+## Getting Help
+
+- Open an issue for release-related problems
+- Check GitHub Actions logs for CI failures
+- Review this guide and workflow files for automation details
\ No newline at end of file
diff --git a/justfile b/justfile
index e3199a6..a480816 100644
--- a/justfile
+++ b/justfile
@@ -169,20 +169,62 @@ flamegraph:
changelog:
git cliff --output CHANGELOG.md
-# Prepare a release
+# Release commands using the release script
+
+# Create a patch release (0.1.0 -> 0.1.1)
+release-patch:
+ ./scripts/release.sh patch
+
+# Create a minor release (0.1.0 -> 0.2.0)
+release-minor:
+ ./scripts/release.sh minor
+
+# Create a major release (0.1.0 -> 1.0.0)
+release-major:
+ ./scripts/release.sh major
+
+# Create a specific version release
+release-version version:
+ ./scripts/release.sh version {{version}}
+
+# Dry run of patch release (see what would happen)
+release-patch-dry:
+ ./scripts/release.sh patch --dry-run
+
+# Dry run of minor release
+release-minor-dry:
+ ./scripts/release.sh minor --dry-run
+
+# Dry run of major release
+release-major-dry:
+ ./scripts/release.sh major --dry-run
+
+# Dry run of specific version release
+release-version-dry version:
+ ./scripts/release.sh version {{version}} --dry-run
+
+# Run all pre-release checks
+release-check:
+ ./scripts/release.sh check
+
+# Generate changelog since last tag
+release-changelog:
+ ./scripts/release.sh changelog
+
+# Create git tag for current version
+release-tag:
+ ./scripts/release.sh tag
+
+# Prepare a release (legacy command - use release-* commands above)
prepare-release version:
- # Update version in Cargo.toml
- sed -i 's/^version = ".*"/version = "{{version}}"/' Cargo.toml
- # Update dependencies to use new version
- cargo update
- # Run full test suite
- just ci
- # Generate changelog
- just changelog
- # Commit changes
- git add .
- git commit -m "chore: prepare release {{version}}"
- git tag -a "v{{version}}" -m "Release {{version}}"
+ @echo "â ī¸ This command is deprecated. Use 'just release-version {{version}}' instead."
+ @echo "The new release commands provide better automation and safety checks."
+ @echo ""
+ @echo "Available release commands:"
+ @echo " just release-patch - Bump patch version"
+ @echo " just release-minor - Bump minor version"
+ @echo " just release-major - Bump major version"
+ @echo " just release-version X.Y.Z - Set specific version"
# Show project statistics
stats:
@@ -221,4 +263,214 @@ init-hooks:
# Remove git hooks
remove-hooks:
rm -f .git/hooks/pre-commit
- echo "Pre-commit hook removed"
\ No newline at end of file
+ echo "Pre-commit hook removed"
+
+# Docker commands
+
+# Build multi-platform Docker image
+docker-build-multiarch:
+ docker buildx create --use --name multiarch || true
+ docker buildx build --platform linux/amd64,linux/arm64 -t docx-mcp:latest .
+
+# Build and tag Docker image for release
+docker-build-release version:
+ docker buildx build --platform linux/amd64,linux/arm64 \
+ -t docx-mcp:{{version}} \
+ -t docx-mcp:latest \
+ -t ghcr.io/hongkongkiwi/docx-mcp:{{version}} \
+ -t ghcr.io/hongkongkiwi/docx-mcp:latest \
+ .
+
+# Push Docker images to registry
+docker-push version:
+ docker push docx-mcp:{{version}}
+ docker push docx-mcp:latest
+ docker push ghcr.io/hongkongkiwi/docx-mcp:{{version}}
+ docker push ghcr.io/hongkongkiwi/docx-mcp:latest
+
+# Run Docker container with volume mount for testing
+docker-test:
+ docker run --rm -it -v $(pwd)/test-docs:/test-docs docx-mcp:latest
+
+# Development environment commands
+
+# Full development setup from scratch
+dev-setup:
+ # Install Rust if not present
+ @if ! command -v rustup >/dev/null 2>&1; then \
+ echo "Installing Rust..."; \
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \
+ source ~/.cargo/env; \
+ fi
+ # Setup toolchain and tools
+ just setup
+ # Initialize git hooks
+ just init-hooks
+ # Build project
+ just build
+ echo "â
Development environment ready!"
+
+# Check system dependencies
+check-deps:
+ @echo "=== System Dependencies Check ==="
+ @echo "Checking required tools..."
+ @command -v rustc >/dev/null && echo "â
Rust compiler found" || echo "â Rust compiler not found"
+ @command -v cargo >/dev/null && echo "â
Cargo found" || echo "â Cargo not found"
+ @command -v git >/dev/null && echo "â
Git found" || echo "â Git not found"
+ @command -v docker >/dev/null && echo "â
Docker found" || echo "â Docker not found"
+ @command -v just >/dev/null && echo "â
Just found" || echo "â Just not found"
+ @echo ""
+ @echo "Optional tools:"
+ @command -v libreoffice >/dev/null && echo "â
LibreOffice found" || echo "â ī¸ LibreOffice not found (optional)"
+ @command -v pdftoppm >/dev/null && echo "â
pdftoppm found" || echo "â ī¸ pdftoppm not found (optional)"
+ @command -v convert >/dev/null && echo "â
ImageMagick convert found" || echo "â ī¸ ImageMagick not found (optional)"
+
+# Cross-compilation commands
+
+# Build for all supported targets
+build-all-targets:
+ # Install targets if not present
+ rustup target add x86_64-unknown-linux-gnu
+ rustup target add x86_64-unknown-linux-musl
+ rustup target add aarch64-unknown-linux-gnu
+ rustup target add x86_64-apple-darwin
+ rustup target add aarch64-apple-darwin
+ rustup target add x86_64-pc-windows-msvc
+ # Build for each target
+ cargo build --release --target x86_64-unknown-linux-gnu --all-features
+ cargo build --release --target x86_64-unknown-linux-musl --all-features
+ cargo build --release --target x86_64-apple-darwin --all-features
+ @echo "â
Built for all available targets"
+
+# Build using cross for Linux targets
+build-cross-linux:
+ cargo install cross --git https://github.com/cross-rs/cross
+ cross build --release --target x86_64-unknown-linux-gnu --all-features
+ cross build --release --target x86_64-unknown-linux-musl --all-features
+ cross build --release --target aarch64-unknown-linux-gnu --all-features
+ cross build --release --target aarch64-unknown-linux-musl --all-features
+
+# Maintenance commands
+
+# Update all dependencies to latest versions
+update-deps:
+ cargo update
+ cargo outdated --depth 1
+
+# Check for security vulnerabilities and update
+security-update:
+ cargo audit fix
+ cargo update
+
+# Clean everything (including registry cache)
+clean-all:
+ cargo clean
+ rm -rf ~/.cargo/registry/cache
+ rm -rf ~/.cargo/git/db
+ docker system prune -f
+
+# Backup project (excluding target and build artifacts)
+backup:
+ #!/usr/bin/env bash
+ BACKUP_NAME="docx-mcp-backup-$(date +%Y%m%d-%H%M%S)"
+ tar czf "${BACKUP_NAME}.tar.gz" \
+ --exclude='target' \
+ --exclude='.git' \
+ --exclude='*.log' \
+ --exclude='*.tmp' \
+ .
+ echo "â
Backup created: ${BACKUP_NAME}.tar.gz"
+
+# Development workflows
+
+# Quick development loop (format, build, test unit, lint)
+dev-loop:
+ just fmt
+ just build
+ just test-unit
+ just clippy
+
+# Full quality check (everything CI runs)
+quality-check:
+ just fmt-check
+ just clippy
+ just test
+ just docs-check
+ just audit
+ just deny
+
+# Continuous development with file watching
+dev-watch:
+ cargo install cargo-watch
+ cargo watch -w src -w tests -x "build" -x "test --lib"
+
+# Performance analysis
+perf-analysis:
+ # Build optimized release
+ cargo build --release --all-features
+ # Run criterion benchmarks
+ cargo bench --all-features
+ # Generate flamegraph if available
+ @if command -v flamegraph >/dev/null 2>&1; then \
+ echo "Generating flamegraph..."; \
+ cargo flamegraph --bin docx-mcp -- --help; \
+ fi
+
+# MCP-specific commands
+
+# Test MCP server functionality
+test-mcp:
+ @echo "Testing MCP server..."
+ # Build the server
+ cargo build --release --all-features
+ # Run basic functionality test
+ python3 example/test_client.py || echo "â MCP test failed"
+
+# Generate MCP documentation
+mcp-docs:
+ @echo "Generating MCP server documentation..."
+ cargo run --bin docx-mcp -- --help > docs/CLI_REFERENCE.md
+ @echo "â
CLI reference updated"
+
+# Example commands
+
+# Run all examples
+run-examples:
+ @echo "Running all examples..."
+ @if [ -f example/test_client.py ]; then python3 example/test_client.py; fi
+ @if [ -f example/automation_example.py ]; then python3 example/automation_example.py; fi
+
+# Generate test documents
+gen-test-docs:
+ @echo "Generating test documents..."
+ mkdir -p test-docs
+ # You could add commands here to generate various test DOCX files
+
+# Utility commands
+
+# Show detailed project info
+info:
+ @echo "=== Project Information ==="
+ @echo "Name: docx-mcp"
+ @echo "Version: $(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')"
+ @echo "Rust version: $(rustc --version)"
+ @echo "Cargo version: $(cargo --version)"
+ @echo ""
+ just stats
+
+# List all available commands with descriptions
+help:
+ @echo "=== Available Commands ==="
+ @just --list
+ @echo ""
+ @echo "=== Release Commands ==="
+ @echo " release-patch - Create patch release (0.1.0 -> 0.1.1)"
+ @echo " release-minor - Create minor release (0.1.0 -> 0.2.0)"
+ @echo " release-major - Create major release (0.1.0 -> 1.0.0)"
+ @echo " release-version X - Create specific version release"
+ @echo " release-*-dry - Dry run versions of above commands"
+ @echo ""
+ @echo "=== Development Workflows ==="
+ @echo " dev-loop - Quick development cycle"
+ @echo " quality-check - Full quality assessment"
+ @echo " dev-setup - Complete development environment setup"
\ No newline at end of file
diff --git a/scripts/release.sh b/scripts/release.sh
new file mode 100755
index 0000000..2af1165
--- /dev/null
+++ b/scripts/release.sh
@@ -0,0 +1,355 @@
+#!/bin/bash
+
+# Release script for docx-mcp
+# This script helps with version management and release preparation
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Helper functions
+info() {
+ echo -e "${BLUE}âšī¸ $1${NC}"
+}
+
+success() {
+ echo -e "${GREEN}â
$1${NC}"
+}
+
+warning() {
+ echo -e "${YELLOW}â ī¸ $1${NC}"
+}
+
+error() {
+ echo -e "${RED}â $1${NC}"
+}
+
+# Check if we're in a git repository
+check_git_repo() {
+ if ! git rev-parse --git-dir > /dev/null 2>&1; then
+ error "Not in a git repository"
+ exit 1
+ fi
+}
+
+# Check if working directory is clean
+check_clean_working_dir() {
+ if ! git diff-index --quiet HEAD --; then
+ error "Working directory is not clean. Please commit or stash your changes."
+ exit 1
+ fi
+}
+
+# Get current version from Cargo.toml
+get_current_version() {
+ grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/'
+}
+
+# Update version in Cargo.toml
+update_version() {
+ local new_version=$1
+ info "Updating version to $new_version"
+
+ # Update Cargo.toml
+ sed -i.bak "s/^version = \".*\"/version = \"$new_version\"/" Cargo.toml
+ rm Cargo.toml.bak
+
+ # Update Cargo.lock
+ cargo update -p docx-mcp
+
+ success "Version updated to $new_version"
+}
+
+# Generate changelog since last tag
+generate_changelog() {
+ local last_tag=$(git tag --sort=-version:refname | head -1)
+ local new_version=$1
+
+ info "Generating changelog since $last_tag"
+
+ if [ -n "$last_tag" ]; then
+ git log --pretty=format:"- %s (%h)" --no-merges ${last_tag}..HEAD > CHANGELOG.tmp
+ else
+ git log --pretty=format:"- %s (%h)" --no-merges > CHANGELOG.tmp
+ fi
+
+ echo "## Release $new_version ($(date +%Y-%m-%d))"
+ echo ""
+ cat CHANGELOG.tmp
+ echo ""
+ rm CHANGELOG.tmp
+}
+
+# Run pre-release checks
+run_checks() {
+ info "Running pre-release checks..."
+
+ # Format check
+ info "Checking code formatting..."
+ cargo fmt --all -- --check
+ success "Code formatting is correct"
+
+ # Clippy check
+ info "Running Clippy..."
+ cargo clippy --all-targets --all-features -- -D warnings
+ success "Clippy checks passed"
+
+ # Tests
+ info "Running tests..."
+ cargo test --all-features
+ success "All tests passed"
+
+ # Build check
+ info "Testing release build..."
+ cargo build --release --all-features
+ success "Release build successful"
+
+ # Package check
+ info "Testing package..."
+ cargo package --dry-run
+ success "Package validation passed"
+}
+
+# Create and push git tag
+create_tag() {
+ local version=$1
+ local tag="v$version"
+
+ info "Creating git tag $tag"
+
+ # Create annotated tag
+ git tag -a "$tag" -m "Release $tag"
+
+ success "Created tag $tag"
+
+ # Ask if user wants to push
+ echo -n "Push tag to origin? [y/N]: "
+ read -r response
+ if [[ "$response" =~ ^[Yy]$ ]]; then
+ git push origin "$tag"
+ success "Tag pushed to origin"
+ else
+ warning "Tag not pushed. Remember to push it manually: git push origin $tag"
+ fi
+}
+
+# Show usage information
+usage() {
+ cat << EOF
+Usage: $0 [COMMAND] [OPTIONS]
+
+Commands:
+ patch Bump patch version (0.1.0 -> 0.1.1)
+ minor Bump minor version (0.1.0 -> 0.2.0)
+ major Bump major version (0.1.0 -> 1.0.0)
+ version X.Y.Z Set specific version
+ check Run pre-release checks only
+ changelog Generate changelog since last tag
+ tag Create git tag for current version
+
+Options:
+ --dry-run Show what would be done without making changes
+ --no-checks Skip pre-release checks (not recommended)
+ --no-tag Don't create git tag
+ --help Show this help message
+
+Examples:
+ $0 patch # Bump to next patch version
+ $0 version 1.0.0 # Set version to 1.0.0
+ $0 check # Run all pre-release checks
+ $0 patch --dry-run # Show what patch release would do
+EOF
+}
+
+# Parse version bump type
+bump_version() {
+ local current_version=$1
+ local bump_type=$2
+
+ # Split version into components
+ IFS='.' read -ra VERSION_PARTS <<< "$current_version"
+ local major=${VERSION_PARTS[0]}
+ local minor=${VERSION_PARTS[1]}
+ local patch=${VERSION_PARTS[2]}
+
+ case $bump_type in
+ "patch")
+ patch=$((patch + 1))
+ ;;
+ "minor")
+ minor=$((minor + 1))
+ patch=0
+ ;;
+ "major")
+ major=$((major + 1))
+ minor=0
+ patch=0
+ ;;
+ *)
+ error "Invalid bump type: $bump_type"
+ exit 1
+ ;;
+ esac
+
+ echo "${major}.${minor}.${patch}"
+}
+
+# Validate version format
+validate_version() {
+ local version=$1
+ if ! [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
+ error "Invalid version format: $version"
+ error "Expected format: X.Y.Z or X.Y.Z-suffix"
+ exit 1
+ fi
+}
+
+# Main script logic
+main() {
+ local command=$1
+ local dry_run=false
+ local no_checks=false
+ local no_tag=false
+
+ # Parse arguments
+ while [[ $# -gt 0 ]]; do
+ case $1 in
+ --dry-run)
+ dry_run=true
+ shift
+ ;;
+ --no-checks)
+ no_checks=true
+ shift
+ ;;
+ --no-tag)
+ no_tag=true
+ shift
+ ;;
+ --help)
+ usage
+ exit 0
+ ;;
+ *)
+ if [ -z "$command" ]; then
+ command=$1
+ elif [ -z "$version_arg" ] && [ "$command" = "version" ]; then
+ version_arg=$1
+ fi
+ shift
+ ;;
+ esac
+ done
+
+ # Check if command provided
+ if [ -z "$command" ]; then
+ usage
+ exit 1
+ fi
+
+ # Basic checks
+ check_git_repo
+
+ if [ "$dry_run" = false ]; then
+ check_clean_working_dir
+ fi
+
+ current_version=$(get_current_version)
+ info "Current version: $current_version"
+
+ case $command in
+ "patch"|"minor"|"major")
+ new_version=$(bump_version "$current_version" "$command")
+ ;;
+ "version")
+ if [ -z "$version_arg" ]; then
+ error "Version argument required for 'version' command"
+ exit 1
+ fi
+ new_version=$version_arg
+ validate_version "$new_version"
+ ;;
+ "check")
+ run_checks
+ success "All pre-release checks passed!"
+ exit 0
+ ;;
+ "changelog")
+ generate_changelog "$current_version"
+ exit 0
+ ;;
+ "tag")
+ if [ "$dry_run" = true ]; then
+ info "Would create tag v$current_version"
+ else
+ create_tag "$current_version"
+ fi
+ exit 0
+ ;;
+ *)
+ error "Unknown command: $command"
+ usage
+ exit 1
+ ;;
+ esac
+
+ info "New version will be: $new_version"
+
+ if [ "$dry_run" = true ]; then
+ warning "DRY RUN MODE - No changes will be made"
+ info "Would update version from $current_version to $new_version"
+ if [ "$no_checks" = false ]; then
+ info "Would run pre-release checks"
+ fi
+ if [ "$no_tag" = false ]; then
+ info "Would create git tag v$new_version"
+ fi
+ exit 0
+ fi
+
+ # Confirm with user
+ echo -n "Proceed with release $new_version? [y/N]: "
+ read -r response
+ if [[ ! "$response" =~ ^[Yy]$ ]]; then
+ warning "Release cancelled"
+ exit 0
+ fi
+
+ # Run pre-release checks
+ if [ "$no_checks" = false ]; then
+ run_checks
+ fi
+
+ # Update version
+ update_version "$new_version"
+
+ # Commit version bump
+ git add Cargo.toml Cargo.lock
+ git commit -m "Release $new_version"
+ success "Version bump committed"
+
+ # Create tag
+ if [ "$no_tag" = false ]; then
+ create_tag "$new_version"
+ fi
+
+ # Generate changelog for reference
+ info "Changelog for release:"
+ generate_changelog "$new_version"
+
+ success "Release $new_version completed!"
+ info "Next steps:"
+ info "1. Push commits: git push origin main"
+ if [ "$no_tag" = false ]; then
+ info "2. Push tag: git push origin v$new_version (if not done already)"
+ fi
+ info "3. GitHub Actions will automatically create the release"
+}
+
+# Run main function with all arguments
+main "$@"
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 47745aa..8d68b06 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@ use mcp_server::{Server, ServerBuilder, ServerOptions};
use mcp_core::ToolManager;
use tracing::info;
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
+use clap::Parser;
mod docx_tools;
mod docx_handler;
@@ -23,8 +24,9 @@ async fn main() -> Result<()> {
.with(EnvFilter::from_default_env())
.init();
- // Load security configuration from environment
- let security_config = security::SecurityConfig::from_env();
+ // Parse command line arguments (which also includes environment variables)
+ let args = security::Args::parse();
+ let security_config = security::SecurityConfig::from_args(args);
info!("Starting DOCX MCP Server - Security: {}", security_config.get_summary());
let docx_provider = DocxToolsProvider::new_with_security(security_config);
diff --git a/src/security.rs b/src/security.rs
index 502622f..582d9d9 100644
--- a/src/security.rs
+++ b/src/security.rs
@@ -2,6 +2,46 @@ use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::env;
use tracing::{debug, info, warn};
+use clap::Parser;
+
+/// Command line arguments for the DOCX MCP server
+#[derive(Parser, Debug)]
+#[command(name = "docx-mcp")]
+#[command(about = "A comprehensive Model Context Protocol (MCP) server for Microsoft Word DOCX file manipulation")]
+#[command(version)]
+pub struct Args {
+ /// Enable readonly mode - only allow viewing operations
+ #[arg(long, env = "DOCX_MCP_READONLY")]
+ pub readonly: bool,
+
+ /// Comma-separated whitelist of allowed commands
+ #[arg(long, env = "DOCX_MCP_WHITELIST", value_delimiter = ',')]
+ pub whitelist: Option>,
+
+ /// Comma-separated blacklist of forbidden commands
+ #[arg(long, env = "DOCX_MCP_BLACKLIST", value_delimiter = ',')]
+ pub blacklist: Option>,
+
+ /// Enable sandbox mode - restrict file operations to temp directory only
+ #[arg(long, env = "DOCX_MCP_SANDBOX")]
+ pub sandbox: bool,
+
+ /// Disable external tools (LibreOffice, etc.)
+ #[arg(long, env = "DOCX_MCP_NO_EXTERNAL_TOOLS")]
+ pub no_external_tools: bool,
+
+ /// Disable network operations
+ #[arg(long, env = "DOCX_MCP_NO_NETWORK")]
+ pub no_network: bool,
+
+ /// Maximum document size in bytes
+ #[arg(long, env = "DOCX_MCP_MAX_SIZE")]
+ pub max_size: Option,
+
+ /// Maximum number of open documents
+ #[arg(long, env = "DOCX_MCP_MAX_DOCS")]
+ pub max_docs: Option,
+}
/// Security configuration for the MCP server
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -47,7 +87,59 @@ impl Default for SecurityConfig {
}
impl SecurityConfig {
- /// Load configuration from environment variables
+ /// Create configuration from command line arguments
+ pub fn from_args(args: Args) -> Self {
+ let mut config = Self::default();
+
+ // Apply command line arguments
+ if args.readonly {
+ config.readonly_mode = true;
+ info!("Running in READONLY mode - only viewing operations allowed");
+ }
+
+ if let Some(whitelist) = args.whitelist {
+ let commands: HashSet = whitelist.into_iter().collect();
+ info!("Command whitelist enabled with {} commands", commands.len());
+ config.command_whitelist = Some(commands);
+ }
+
+ if let Some(blacklist) = args.blacklist {
+ let commands: HashSet = blacklist.into_iter().collect();
+ info!("Command blacklist enabled with {} blocked commands", commands.len());
+ config.command_blacklist = Some(commands);
+ }
+
+ if args.sandbox {
+ config.sandbox_mode = true;
+ config.allow_external_tools = false;
+ config.allow_network = false;
+ info!("Running in SANDBOX mode - restricted file operations");
+ }
+
+ if args.no_external_tools {
+ config.allow_external_tools = false;
+ info!("External tools disabled");
+ }
+
+ if args.no_network {
+ config.allow_network = false;
+ info!("Network operations disabled");
+ }
+
+ if let Some(size) = args.max_size {
+ config.max_document_size = size;
+ info!("Max document size set to {} bytes", size);
+ }
+
+ if let Some(max) = args.max_docs {
+ config.max_open_documents = max;
+ info!("Max open documents set to {}", max);
+ }
+
+ config
+ }
+
+ /// Load configuration from environment variables (deprecated, use from_args instead)
pub fn from_env() -> Self {
let mut config = Self::default();