diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c6aa52..8cf7b34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,93 +67,3 @@ jobs: with: version: latest args: --timeout=5m - - build-desktop: - name: Build Desktop Apps - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - platform: linux/amd64 - name: linux-amd64 - ext: "" - - os: macos-latest - platform: darwin/amd64 - name: darwin-amd64 - ext: ".app" - - os: macos-latest - platform: darwin/arm64 - name: darwin-arm64 - ext: ".app" - - os: windows-latest - platform: windows/amd64 - name: windows-amd64 - ext: ".exe" - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install Wails - run: go install github.com/wailsapp/wails/v2/cmd/wails@latest - - - name: Install Linux dependencies - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev - - - name: Install Windows dependencies - if: matrix.os == 'windows-latest' - run: | - choco install mingw -y - choco install nsis -y - - - name: Install frontend dependencies - working-directory: frontend - run: npm ci - - - name: Build Wails app - run: | - wails build -platform ${{ matrix.platform }} -clean - - - name: Package artifacts (Linux) - if: matrix.os == 'ubuntu-latest' - run: | - mkdir -p artifacts - cp build/bin/ml-notes artifacts/ml-notes-${{ matrix.name }} - chmod +x artifacts/ml-notes-${{ matrix.name }} - - - name: Package artifacts (macOS) - if: matrix.os == 'macos-latest' - run: | - mkdir -p artifacts - cp -r "build/bin/ML Notes.app" "artifacts/ML Notes-${{ matrix.name }}.app" - - - name: Package artifacts (Windows) - if: matrix.os == 'windows-latest' - run: | - mkdir -p artifacts - cp build/bin/ml-notes.exe artifacts/ml-notes-${{ matrix.name }}.exe - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: ml-notes-desktop-${{ matrix.name }} - path: artifacts/* - retention-days: 30 diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index f6d7cba..9846854 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -68,8 +68,8 @@ jobs: run: | go test -v -race ./... - build: - name: Build Release Binaries + build-cli: + name: Build CLI Binaries needs: validate runs-on: ${{ matrix.os }} strategy: @@ -95,16 +95,16 @@ jobs: goarch: amd64 name: windows-amd64 ext: ".exe" - + steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Setup Go uses: actions/setup-go@v5 with: go-version: "1.22" - + - name: Setup Windows build environment if: matrix.goos == 'windows' shell: bash @@ -117,16 +117,16 @@ jobs: echo "C:\\tools\\msys64\\mingw64\\bin" >> $GITHUB_PATH # Verify installation C:/tools/msys64/mingw64/bin/gcc --version || echo "GCC not found" - - - name: Build binary + + - name: Build CLI binary shell: bash run: | VERSION="${{ needs.validate.outputs.version }}" BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') GIT_COMMIT=$(git rev-parse --short HEAD) - + mkdir -p dist - + if [ "${{ matrix.goos }}" = "windows" ]; then # Set up CGO environment for Windows with MSYS2 export CGO_ENABLED=1 @@ -135,110 +135,281 @@ jobs: export PKG_CONFIG_PATH="C:/tools/msys64/mingw64/lib/pkgconfig" export PATH="C:/tools/msys64/mingw64/bin:$PATH" fi - + + # Build CLI binary CGO_ENABLED=1 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build \ -ldflags "-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT}" \ - -o dist/ml-notes-${{ matrix.name }}${{ matrix.ext }} . - - - name: Upload artifact + -o dist/ml-notes-cli-${{ matrix.name }}${{ matrix.ext }} ./app/cli + + - name: Upload CLI artifact + uses: actions/upload-artifact@v4 + with: + name: ml-notes-cli-${{ matrix.name }} + path: dist/ml-notes-cli-${{ matrix.name }}${{ matrix.ext }} + retention-days: 1 + + build-desktop: + name: Build Desktop Binaries + needs: validate + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + platform: linux/amd64 + name: linux-amd64 + - os: macos-latest + platform: darwin/universal + name: darwin-universal + - os: windows-latest + platform: windows/amd64 + name: windows-amd64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install Wails + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Setup Windows build environment + if: matrix.os == 'windows-latest' + shell: bash + run: | + # Install MSYS2 for complete build environment + choco install msys2 -y + # Initialize MSYS2 and install required packages + C:/tools/msys64/usr/bin/bash -lc 'pacman --noconfirm -S mingw-w64-x86_64-gcc mingw-w64-x86_64-pkg-config mingw-w64-x86_64-sqlite3' + # Add MSYS2 MinGW64 to PATH + echo "C:\\tools\\msys64\\mingw64\\bin" >> $GITHUB_PATH + + - name: Setup Linux dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev + + - name: Install frontend dependencies + if: hashFiles('frontend/package.json') != '' + run: | + if [ -d "frontend" ]; then + cd frontend + npm install + fi + + - name: Build desktop application + shell: bash + run: | + VERSION="${{ needs.validate.outputs.version }}" + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + GIT_COMMIT=$(git rev-parse --short HEAD) + + if [ "${{ matrix.os }}" = "windows-latest" ]; then + # Set up CGO environment for Windows with MSYS2 + export CGO_ENABLED=1 + export CC=C:/tools/msys64/mingw64/bin/gcc.exe + export CXX=C:/tools/msys64/mingw64/bin/g++.exe + export PKG_CONFIG_PATH="C:/tools/msys64/mingw64/lib/pkgconfig" + export PATH="C:/tools/msys64/mingw64/bin:$PATH" + fi + + # Build with Wails + wails build -clean -platform ${{ matrix.platform }} \ + -ldflags "-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT}" + + - name: Prepare desktop artifacts + shell: bash + run: | + mkdir -p dist + + if [ "${{ matrix.os }}" = "windows-latest" ]; then + cp build/bin/ml-notes.exe dist/ml-notes-${{ matrix.name }}.exe + elif [ "${{ matrix.os }}" = "macos-latest" ]; then + # For macOS, copy the app bundle + if [ -d "build/bin/ml-notes.app" ]; then + tar -czf dist/ml-notes-${{ matrix.name }}.tar.gz -C build/bin ml-notes.app + else + cp build/bin/ml-notes dist/ml-notes-${{ matrix.name }} + fi + else + cp build/bin/ml-notes dist/ml-notes-${{ matrix.name }} + fi + + - name: Upload desktop artifacts uses: actions/upload-artifact@v4 with: - name: ml-notes-${{ matrix.name }} - path: dist/ml-notes-${{ matrix.name }}${{ matrix.ext }} + name: ml-notes-desktop-${{ matrix.name }} + path: | + dist/ml-notes-${{ matrix.name }}* retention-days: 1 release: name: Create Manual Release - needs: [validate, build] + needs: [validate, build-cli, build-desktop] runs-on: ubuntu-latest - + 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: Create release archives run: | VERSION="v${{ needs.validate.outputs.version }}" mkdir -p dist/release - - # Move artifacts to dist directory and create archives - find artifacts -name "ml-notes-*" -exec cp {} dist/ \; - - # Linux AMD64 - if [ -f "dist/ml-notes-linux-amd64" ]; then - tar -czf dist/release/ml-notes-${VERSION}-linux-amd64.tar.gz -C dist ml-notes-linux-amd64 + + # Move all artifacts to dist directory + find artifacts -type f -name "ml-notes-*" -exec cp {} dist/ \; + + # List available files for debugging + echo "Available files:" + ls -la dist/ + + # Create CLI packages + + # Linux CLI + if [ -f "dist/ml-notes-cli-linux-amd64" ]; then + tar -czf dist/release/ml-notes-cli-${VERSION}-linux-amd64.tar.gz -C dist ml-notes-cli-linux-amd64 fi - - # macOS AMD64 (Intel) - if [ -f "dist/ml-notes-darwin-amd64" ]; then - tar -czf dist/release/ml-notes-${VERSION}-darwin-amd64.tar.gz -C dist ml-notes-darwin-amd64 + + # macOS CLI (Intel) + if [ -f "dist/ml-notes-cli-darwin-amd64" ]; then + tar -czf dist/release/ml-notes-cli-${VERSION}-darwin-amd64.tar.gz -C dist ml-notes-cli-darwin-amd64 fi - - # macOS ARM64 (Apple Silicon) - if [ -f "dist/ml-notes-darwin-arm64" ]; then - tar -czf dist/release/ml-notes-${VERSION}-darwin-arm64.tar.gz -C dist ml-notes-darwin-arm64 + + # macOS CLI (Apple Silicon) + if [ -f "dist/ml-notes-cli-darwin-arm64" ]; then + tar -czf dist/release/ml-notes-cli-${VERSION}-darwin-arm64.tar.gz -C dist ml-notes-cli-darwin-arm64 fi - - # Windows AMD64 + + # Windows CLI + if [ -f "dist/ml-notes-cli-windows-amd64.exe" ]; then + cd dist && zip release/ml-notes-cli-${VERSION}-windows-amd64.zip ml-notes-cli-windows-amd64.exe + cd .. + fi + + # Create Desktop packages + + # Linux Desktop + if [ -f "dist/ml-notes-linux-amd64" ]; then + tar -czf dist/release/ml-notes-desktop-${VERSION}-linux-amd64.tar.gz -C dist ml-notes-linux-amd64 + fi + + # macOS Desktop (Universal) + if [ -f "dist/ml-notes-darwin-universal.tar.gz" ]; then + cp dist/ml-notes-darwin-universal.tar.gz dist/release/ml-notes-desktop-${VERSION}-darwin-universal.tar.gz + elif [ -f "dist/ml-notes-darwin-universal" ]; then + tar -czf dist/release/ml-notes-desktop-${VERSION}-darwin-universal.tar.gz -C dist ml-notes-darwin-universal + fi + + # Windows Desktop if [ -f "dist/ml-notes-windows-amd64.exe" ]; then - cd dist && zip release/ml-notes-${VERSION}-windows-amd64.zip ml-notes-windows-amd64.exe + cd dist && zip release/ml-notes-desktop-${VERSION}-windows-amd64.zip ml-notes-windows-amd64.exe cd .. fi - + # Create checksums cd dist/release sha256sum * > checksums.txt - + # Create installation README cat > README.md << EOF # ML Notes ${VERSION} - Release Packages - + + ML Notes provides two distinct binaries: + - **CLI Binary** (\`ml-notes-cli\`): Command-line interface for terminal usage, automation, and MCP server + - **Desktop App** (\`ml-notes\`): Native desktop application with GUI + ## Download the right package for your platform: - - ### Linux - - **Linux x64**: \`ml-notes-${VERSION}-linux-amd64.tar.gz\` - - ### macOS - - **macOS Intel**: \`ml-notes-${VERSION}-darwin-amd64.tar.gz\` - - **macOS Apple Silicon**: \`ml-notes-${VERSION}-darwin-arm64.tar.gz\` - - ### Windows - - **Windows x64**: \`ml-notes-${VERSION}-windows-amd64.zip\` - + + ### Command-Line Interface (CLI) + - **Linux x64**: \`ml-notes-cli-${VERSION}-linux-amd64.tar.gz\` + - **macOS Intel**: \`ml-notes-cli-${VERSION}-darwin-amd64.tar.gz\` + - **macOS Apple Silicon**: \`ml-notes-cli-${VERSION}-darwin-arm64.tar.gz\` + - **Windows x64**: \`ml-notes-cli-${VERSION}-windows-amd64.zip\` + + ### Desktop Application + - **Linux x64**: \`ml-notes-desktop-${VERSION}-linux-amd64.tar.gz\` + - **macOS Universal**: \`ml-notes-desktop-${VERSION}-darwin-universal.tar.gz\` + - **Windows x64**: \`ml-notes-desktop-${VERSION}-windows-amd64.zip\` + ## Installation - - ### Linux/macOS - 1. Download the appropriate \`.tar.gz\` file - 2. Extract: \`tar -xzf ml-notes-${VERSION}-.tar.gz\` - 3. Install: \`sudo mv ml-notes- /usr/local/bin/ml-notes\` - 4. Initialize: \`ml-notes init\` - - ### Windows - 1. Download \`ml-notes-${VERSION}-windows-amd64.zip\` + + ### CLI Binary (Linux/macOS) + 1. Download the appropriate CLI \`.tar.gz\` file + 2. Extract: \`tar -xzf ml-notes-cli-${VERSION}-.tar.gz\` + 3. Install: \`sudo mv ml-notes-cli- /usr/local/bin/ml-notes-cli\` + 4. Initialize: \`ml-notes-cli init\` + + ### CLI Binary (Windows) + 1. Download \`ml-notes-cli-${VERSION}-windows-amd64.zip\` 2. Extract the ZIP file 3. Add the extracted directory to your PATH - 4. Initialize: \`ml-notes init\` - + 4. Initialize: \`ml-notes-cli init\` + + ### Desktop Application (Linux/macOS) + 1. Download the appropriate desktop \`.tar.gz\` file + 2. Extract: \`tar -xzf ml-notes-desktop-${VERSION}-.tar.gz\` + 3. Linux: \`sudo mv ml-notes- /usr/local/bin/ml-notes\` + 4. macOS: Move \`ml-notes.app\` to \`/Applications/\` + + ### Desktop Application (Windows) + 1. Download \`ml-notes-desktop-${VERSION}-windows-amd64.zip\` + 2. Extract the ZIP file + 3. Run \`ml-notes.exe\` or add to PATH + + ## Usage + + ### CLI Commands + \`\`\`bash + ml-notes-cli --version # Check version + ml-notes-cli init # Initialize configuration + ml-notes-cli add -t "Title" -c "Content" # Add note + ml-notes-cli serve # Start web interface + ml-notes-cli mcp # Run MCP server for Claude + \`\`\` + + ### Desktop Application + - Launch the desktop app and enjoy the native GUI experience + - All features available through visual interface + - Shares same configuration and data with CLI + ## Verification - + Verify your installation: \`\`\`bash - ml-notes --version + ml-notes-cli --version # For CLI binary + ml-notes --version # For desktop binary (if in PATH) \`\`\` - + ## Checksums - + Verify download integrity using \`checksums.txt\`: \`\`\`bash sha256sum -c checksums.txt \`\`\` + + ## Documentation + + - Full documentation: https://github.com/streed/ml-notes + - CLI usage guide: See README.md in the repository + - Desktop app features: Built-in help and tooltips EOF - name: Generate changelog diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5ca4ce..2a68be7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -151,8 +151,8 @@ jobs: run: | go test -v -race ./... - build: - name: Build Release Binaries + build-cli: + name: Build CLI Binaries if: needs.release.outputs.has_changes == 'true' needs: release runs-on: ${{ matrix.os }} @@ -179,16 +179,16 @@ jobs: goarch: amd64 name: windows-amd64 ext: ".exe" - + steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Setup Go uses: actions/setup-go@v5 with: go-version: "1.22" - + - name: Setup Windows build environment if: matrix.goos == 'windows' shell: bash @@ -201,16 +201,16 @@ jobs: echo "C:\\tools\\msys64\\mingw64\\bin" >> $GITHUB_PATH # Verify installation C:/tools/msys64/mingw64/bin/gcc --version || echo "GCC not found" - - - name: Build binary + + - name: Build CLI binary shell: bash run: | VERSION="${{ needs.release.outputs.version_without_v }}" BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') GIT_COMMIT=$(git rev-parse --short HEAD) - + mkdir -p dist - + if [ "${{ matrix.goos }}" = "windows" ]; then # Set up CGO environment for Windows with MSYS2 export CGO_ENABLED=1 @@ -219,109 +219,281 @@ jobs: export PKG_CONFIG_PATH="C:/tools/msys64/mingw64/lib/pkgconfig" export PATH="C:/tools/msys64/mingw64/bin:$PATH" fi - + + # Build CLI binary CGO_ENABLED=1 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build \ -ldflags "-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT}" \ - -o dist/ml-notes-${{ matrix.name }}${{ matrix.ext }} . - - - name: Upload artifact + -o dist/ml-notes-cli-${{ matrix.name }}${{ matrix.ext }} ./app/cli + + - name: Upload CLI artifact uses: actions/upload-artifact@v4 with: - name: ml-notes-${{ matrix.name }} - path: dist/ml-notes-${{ matrix.name }}${{ matrix.ext }} + name: ml-notes-cli-${{ matrix.name }} + path: dist/ml-notes-cli-${{ matrix.name }}${{ matrix.ext }} + retention-days: 1 + + build-desktop: + name: Build Desktop Binaries + if: needs.release.outputs.has_changes == 'true' + needs: release + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + platform: linux/amd64 + name: linux-amd64 + - os: macos-latest + platform: darwin/universal + name: darwin-universal + - os: windows-latest + platform: windows/amd64 + name: windows-amd64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install Wails + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Setup Windows build environment + if: matrix.os == 'windows-latest' + shell: bash + run: | + # Install MSYS2 for complete build environment + choco install msys2 -y + # Initialize MSYS2 and install required packages + C:/tools/msys64/usr/bin/bash -lc 'pacman --noconfirm -S mingw-w64-x86_64-gcc mingw-w64-x86_64-pkg-config mingw-w64-x86_64-sqlite3' + # Add MSYS2 MinGW64 to PATH + echo "C:\\tools\\msys64\\mingw64\\bin" >> $GITHUB_PATH + + - name: Setup Linux dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev + + - name: Install frontend dependencies + if: hashFiles('frontend/package.json') != '' + run: | + if [ -d "frontend" ]; then + cd frontend + npm install + fi + + - name: Build desktop application + shell: bash + run: | + VERSION="${{ needs.release.outputs.version_without_v }}" + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + GIT_COMMIT=$(git rev-parse --short HEAD) + + if [ "${{ matrix.os }}" = "windows-latest" ]; then + # Set up CGO environment for Windows with MSYS2 + export CGO_ENABLED=1 + export CC=C:/tools/msys64/mingw64/bin/gcc.exe + export CXX=C:/tools/msys64/mingw64/bin/g++.exe + export PKG_CONFIG_PATH="C:/tools/msys64/mingw64/lib/pkgconfig" + export PATH="C:/tools/msys64/mingw64/bin:$PATH" + fi + + # Build with Wails + wails build -clean -platform ${{ matrix.platform }} \ + -ldflags "-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT}" + + - name: Prepare desktop artifacts + shell: bash + run: | + mkdir -p dist + + if [ "${{ matrix.os }}" = "windows-latest" ]; then + cp build/bin/ml-notes.exe dist/ml-notes-${{ matrix.name }}.exe + elif [ "${{ matrix.os }}" = "darwin-latest" ]; then + # For macOS, copy the app bundle + if [ -d "build/bin/ml-notes.app" ]; then + tar -czf dist/ml-notes-${{ matrix.name }}.tar.gz -C build/bin ml-notes.app + else + cp build/bin/ml-notes dist/ml-notes-${{ matrix.name }} + fi + else + cp build/bin/ml-notes dist/ml-notes-${{ matrix.name }} + fi + + - name: Upload desktop artifacts + uses: actions/upload-artifact@v4 + with: + name: ml-notes-desktop-${{ matrix.name }} + path: | + dist/ml-notes-${{ matrix.name }}* retention-days: 1 package: name: Create Release Packages if: needs.release.outputs.has_changes == 'true' - needs: [release, build] + needs: [release, build-cli, build-desktop] runs-on: ubuntu-latest - + steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts - + - name: Create release archives run: | VERSION="${{ needs.release.outputs.version }}" mkdir -p dist/release - - # Move artifacts to dist directory and create archives - find artifacts -name "ml-notes-*" -exec cp {} dist/ \; - - # Linux AMD64 - if [ -f "dist/ml-notes-linux-amd64" ]; then - tar -czf dist/release/ml-notes-${VERSION}-linux-amd64.tar.gz -C dist ml-notes-linux-amd64 + + # Move all artifacts to dist directory + find artifacts -type f -name "ml-notes-*" -exec cp {} dist/ \; + + # List available files for debugging + echo "Available files:" + ls -la dist/ + + # Create CLI packages + + # Linux CLI + if [ -f "dist/ml-notes-cli-linux-amd64" ]; then + tar -czf dist/release/ml-notes-cli-${VERSION}-linux-amd64.tar.gz -C dist ml-notes-cli-linux-amd64 fi - - # macOS AMD64 (Intel) - if [ -f "dist/ml-notes-darwin-amd64" ]; then - tar -czf dist/release/ml-notes-${VERSION}-darwin-amd64.tar.gz -C dist ml-notes-darwin-amd64 + + # macOS CLI (Intel) + if [ -f "dist/ml-notes-cli-darwin-amd64" ]; then + tar -czf dist/release/ml-notes-cli-${VERSION}-darwin-amd64.tar.gz -C dist ml-notes-cli-darwin-amd64 fi - - # macOS ARM64 (Apple Silicon) - if [ -f "dist/ml-notes-darwin-arm64" ]; then - tar -czf dist/release/ml-notes-${VERSION}-darwin-arm64.tar.gz -C dist ml-notes-darwin-arm64 + + # macOS CLI (Apple Silicon) + if [ -f "dist/ml-notes-cli-darwin-arm64" ]; then + tar -czf dist/release/ml-notes-cli-${VERSION}-darwin-arm64.tar.gz -C dist ml-notes-cli-darwin-arm64 fi - - # Windows AMD64 + + # Windows CLI + if [ -f "dist/ml-notes-cli-windows-amd64.exe" ]; then + cd dist && zip release/ml-notes-cli-${VERSION}-windows-amd64.zip ml-notes-cli-windows-amd64.exe + cd .. + fi + + # Create Desktop packages + + # Linux Desktop + if [ -f "dist/ml-notes-linux-amd64" ]; then + tar -czf dist/release/ml-notes-desktop-${VERSION}-linux-amd64.tar.gz -C dist ml-notes-linux-amd64 + fi + + # macOS Desktop (Universal) + if [ -f "dist/ml-notes-darwin-universal.tar.gz" ]; then + cp dist/ml-notes-darwin-universal.tar.gz dist/release/ml-notes-desktop-${VERSION}-darwin-universal.tar.gz + elif [ -f "dist/ml-notes-darwin-universal" ]; then + tar -czf dist/release/ml-notes-desktop-${VERSION}-darwin-universal.tar.gz -C dist ml-notes-darwin-universal + fi + + # Windows Desktop if [ -f "dist/ml-notes-windows-amd64.exe" ]; then - cd dist && zip release/ml-notes-${VERSION}-windows-amd64.zip ml-notes-windows-amd64.exe + cd dist && zip release/ml-notes-desktop-${VERSION}-windows-amd64.zip ml-notes-windows-amd64.exe cd .. fi - + # Create checksums cd dist/release sha256sum * > checksums.txt - + # Create installation README cat > README.md << EOF # ML Notes ${VERSION} - Release Packages - + + ML Notes provides two distinct binaries: + - **CLI Binary** (\`ml-notes-cli\`): Command-line interface for terminal usage, automation, and MCP server + - **Desktop App** (\`ml-notes\`): Native desktop application with GUI + ## Download the right package for your platform: - - ### Linux - - **Linux x64**: \`ml-notes-${VERSION}-linux-amd64.tar.gz\` - - ### macOS - - **macOS Intel**: \`ml-notes-${VERSION}-darwin-amd64.tar.gz\` - - **macOS Apple Silicon**: \`ml-notes-${VERSION}-darwin-arm64.tar.gz\` - - ### Windows - - **Windows x64**: \`ml-notes-${VERSION}-windows-amd64.zip\` - + + ### Command-Line Interface (CLI) + - **Linux x64**: \`ml-notes-cli-${VERSION}-linux-amd64.tar.gz\` + - **macOS Intel**: \`ml-notes-cli-${VERSION}-darwin-amd64.tar.gz\` + - **macOS Apple Silicon**: \`ml-notes-cli-${VERSION}-darwin-arm64.tar.gz\` + - **Windows x64**: \`ml-notes-cli-${VERSION}-windows-amd64.zip\` + + ### Desktop Application + - **Linux x64**: \`ml-notes-desktop-${VERSION}-linux-amd64.tar.gz\` + - **macOS Universal**: \`ml-notes-desktop-${VERSION}-darwin-universal.tar.gz\` + - **Windows x64**: \`ml-notes-desktop-${VERSION}-windows-amd64.zip\` + ## Installation - - ### Linux/macOS - 1. Download the appropriate \`.tar.gz\` file - 2. Extract: \`tar -xzf ml-notes-${VERSION}-.tar.gz\` - 3. Install: \`sudo mv ml-notes- /usr/local/bin/ml-notes\` - 4. Initialize: \`ml-notes init\` - - ### Windows - 1. Download \`ml-notes-${VERSION}-windows-amd64.zip\` + + ### CLI Binary (Linux/macOS) + 1. Download the appropriate CLI \`.tar.gz\` file + 2. Extract: \`tar -xzf ml-notes-cli-${VERSION}-.tar.gz\` + 3. Install: \`sudo mv ml-notes-cli- /usr/local/bin/ml-notes-cli\` + 4. Initialize: \`ml-notes-cli init\` + + ### CLI Binary (Windows) + 1. Download \`ml-notes-cli-${VERSION}-windows-amd64.zip\` 2. Extract the ZIP file 3. Add the extracted directory to your PATH - 4. Initialize: \`ml-notes init\` - + 4. Initialize: \`ml-notes-cli init\` + + ### Desktop Application (Linux/macOS) + 1. Download the appropriate desktop \`.tar.gz\` file + 2. Extract: \`tar -xzf ml-notes-desktop-${VERSION}-.tar.gz\` + 3. Linux: \`sudo mv ml-notes- /usr/local/bin/ml-notes\` + 4. macOS: Move \`ml-notes.app\` to \`/Applications/\` + + ### Desktop Application (Windows) + 1. Download \`ml-notes-desktop-${VERSION}-windows-amd64.zip\` + 2. Extract the ZIP file + 3. Run \`ml-notes.exe\` or add to PATH + + ## Usage + + ### CLI Commands + \`\`\`bash + ml-notes-cli --version # Check version + ml-notes-cli init # Initialize configuration + ml-notes-cli add -t "Title" -c "Content" # Add note + ml-notes-cli serve # Start web interface + ml-notes-cli mcp # Run MCP server for Claude + \`\`\` + + ### Desktop Application + - Launch the desktop app and enjoy the native GUI experience + - All features available through visual interface + - Shares same configuration and data with CLI + ## Verification - + Verify your installation: \`\`\`bash - ml-notes --version + ml-notes-cli --version # For CLI binary + ml-notes --version # For desktop binary (if in PATH) \`\`\` - + ## Checksums - + Verify download integrity using \`checksums.txt\`: \`\`\`bash sha256sum -c checksums.txt \`\`\` + + ## Documentation + + - Full documentation: https://github.com/streed/ml-notes + - CLI usage guide: See README.md in the repository + - Desktop app features: Built-in help and tooltips EOF - name: Upload release artifacts diff --git a/README.md b/README.md index ee5f1cf..f256c66 100644 --- a/README.md +++ b/README.md @@ -4,28 +4,125 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) -A powerful command-line note-taking application with semantic vector search capabilities, powered by SQLite and lil-rag for intelligent search. +A powerful note-taking application with semantic vector search capabilities, powered by SQLite and lil-rag for intelligent search. Available as both a command-line interface (CLI) and a modern desktop application. ## ✨ Features -- 📝 **Complete Note Management** - Create, edit, delete, and organize notes with powerful CLI tools +### 🖥️ **Dual Interface Options** +- 📱 **Desktop Application** - Modern native desktop app built with Wails framework +- 💻 **Command-Line Interface** - Full-featured CLI for terminal enthusiasts and automation + +### 📝 **Core Functionality** +- 📝 **Complete Note Management** - Create, edit, delete, and organize notes with powerful tools - 🌐 **Website Import** - Import web pages as notes with headless browser support and image URL preservation - 🌐 **Modern Web Interface** - Beautiful, responsive web UI with real-time markdown preview and graph visualization - 🏷️ **Smart Tagging System** - Organize notes with tags, search by tags, and manage tag collections - 🔍 **Triple Search Methods** - Semantic vector search, traditional text search, and tag-based search - 📊 **Interactive Graph Visualization** - Explore relationships between notes with D3.js-powered graph views + +### 🚀 **Performance & Integration** - 🚀 **Fast & Lightweight** - Built with Go and SQLite for maximum performance - 🔌 **Lil-Rag Integration** - Advanced semantic search with project-aware namespacing - 📊 **Smart Search Isolation** - Project-scoped search prevents cross-contamination between different note collections - 🧠 **AI-Powered Analysis** - Deep analysis with custom prompts and reasoning visibility - ✏️ **Advanced Editor Features** - Split-pane markdown editor with synchronized scrolling and focus-based behavior + +### 🛠️ **Developer Features** - 🛠️ **Highly Configurable** - Customize everything from storage paths to AI models - 🐛 **Debug Support** - Built-in debugging for troubleshooting and development - 🤖 **MCP Server** - Model Context Protocol server for LLM integration (Claude Desktop) - 🔄 **Smart Reindexing** - Automatic vector index management and optimization +## 🚀 Binaries Overview + +ML Notes provides two distinct binaries to suit different user preferences and workflows: + +### 📟 CLI Binary (`ml-notes-cli`) + +The command-line interface provides full access to all ML Notes functionality through terminal commands. + +**Key Features:** +- 🖥️ **Pure Terminal Interface** - Works entirely in your terminal without GUI dependencies +- 🤖 **Automation Friendly** - Perfect for scripts, CI/CD pipelines, and automated workflows +- 🌐 **Web Server Mode** - Built-in web server for browser-based note management +- 🔧 **Configuration Management** - Initialize and manage settings via command line +- 📊 **MCP Server** - Run as Model Context Protocol server for LLM integration +- 🚀 **Lightweight** - Minimal resource usage, fast startup + +**Use Cases:** +- Daily note-taking via terminal commands +- Automated note processing and analysis +- Server deployments and headless environments +- Integration with text editors and development workflows +- Claude Desktop integration via MCP server + +**Example Commands:** +```bash +# Initialize configuration +ml-notes-cli init --interactive + +# Add a note +ml-notes-cli add -t "Meeting Notes" -c "Important project updates" + +# Search notes +ml-notes-cli search --vector "machine learning concepts" + +# Start web interface +ml-notes-cli serve --port 21212 + +# Run as MCP server +ml-notes-cli mcp +``` + +### 🖥️ Desktop Application (`ml-notes`) + +A modern desktop application built with the Wails framework, providing a native GUI experience. + +**Key Features:** +- 🖱️ **Native Desktop UI** - Modern desktop interface with native OS integration +- 📱 **Cross-Platform** - Runs on Linux, macOS, and Windows with native look and feel +- 🎨 **Rich Interface** - Advanced UI components, drag-and-drop, and visual interactions +- 🔄 **Real-Time Updates** - Live note editing and instant search results +- 📊 **Visual Analytics** - Built-in graph visualization and interactive charts +- 💾 **Local Storage** - All data stored locally with offline capability + +**Use Cases:** +- Visual note-taking and organization +- Interactive graph exploration and analysis +- Desktop productivity workflows +- Users who prefer GUI over command-line interfaces +- Rich text editing with live preview + +**Technical Details:** +- Built with [Wails v2](https://wails.io/) framework +- Go backend with modern web frontend +- Native system integration (notifications, file dialogs, etc.) +- Embedded web server for hybrid functionality +- Shared codebase with CLI for consistent feature parity + +### 🔄 Choosing the Right Binary + +| Feature | CLI (`ml-notes-cli`) | Desktop (`ml-notes`) | +|---------|---------------------|---------------------| +| **Interface** | Terminal-based | Native desktop GUI | +| **Resource Usage** | Minimal | Moderate | +| **Automation** | Excellent | Limited | +| **Visual Features** | Web-based | Native desktop | +| **Headless Mode** | ✅ Yes | ❌ No | +| **Cross-Platform** | ✅ Yes | ✅ Yes | +| **MCP Server** | ✅ Yes | ❌ No | +| **Web Interface** | ✅ Built-in | ✅ Embedded | + +**Recommendation:** +- Use **CLI** for automation, server deployments, terminal workflows, and MCP integration +- Use **Desktop** for visual note-taking, interactive exploration, and GUI-based workflows +- Both binaries can coexist and share the same configuration and data files + ## 📋 Table of Contents +- [Binaries Overview](#binaries-overview) + - [CLI Binary (ml-notes-cli)](#cli-binary-ml-notes-cli) + - [Desktop Application (ml-notes)](#desktop-application-ml-notes) - [Installation](#installation) - [From Source](#from-source) - [Pre-built Binaries](#pre-built-binaries) @@ -655,60 +752,87 @@ Add ML Notes to your Claude Desktop configuration: { "mcpServers": { "ml-notes": { - "command": "ml-notes", + "command": "ml-notes-cli", "args": ["mcp"] } } } ``` +**Important:** Use `ml-notes-cli` (the CLI binary) for MCP server functionality, not the desktop `ml-notes` binary. + 3. Restart Claude Desktop ### Available Tools The MCP server provides the following tools to LLMs: -#### Note Management -- **add_note** - Create a new note with title, content, and optional tags -- **get_note** - Retrieve a specific note by ID (includes tags) -- **update_note** - Modify existing note title, content, or tags -- **delete_note** - Remove a note from the database -- **list_notes** - List notes with pagination support (shows tags) - -#### Tag Management -- **list_tags** - List all tags in the system -- **update_note_tags** - Update tags for a specific note - -#### Search Capabilities -- **search_notes** - Search using vector similarity, text matching, or tag search - - Supports semantic vector search, keyword search, and tag-based search - - Configurable result limits - - Automatically uses best search method - - Tag search with comma-separated tag lists - -### Resources - -The MCP server exposes these resources: -- `notes://recent` - Get the most recently created notes -- `notes://stats` - Database statistics and configuration -- `notes://config` - Current ML Notes configuration -- `notes://note/{id}` - Access specific note by ID - -### Prompts - -Pre-configured prompts for common tasks: -- **search_notes** - Structured search prompt with query parameters -- **analyze_notes** - Generate AI-powered analysis of your note collection +#### Note Management (6 tools) +- **add_note** - Create new notes with optional tags +- **get_note** - Retrieve specific notes by ID +- **update_note** - Modify existing notes and tags +- **delete_note** - Remove notes from database +- **list_notes** - List notes with pagination + +#### Tag Management (2 tools) +- **list_tags** - View all available tags +- **update_note_tags** - Manage note tags + +#### AI-Powered Features (2 tools) +- **suggest_tags** - AI-powered tag suggestions +- **auto_tag_note** - Automatically apply AI-generated tags + +#### Enhanced Search Capabilities +- **search_notes** - Enhanced search with multiple modes: + - **Vector search** - Semantic similarity using lil-rag + - **Text search** - Traditional keyword search + - **Tag search** - Filter by comma-separated tags + - **Auto mode** - Intelligent search type selection + - Configurable output format (summaries or full content) + - Smart result limits (max 100, type-specific defaults) + +### Resources (6 available) + +The MCP server exposes these comprehensive resources: +- **notes://recent** - Most recently created notes with metadata +- **notes://note/{id}** - Individual note access by ID (supports URI templates) +- **notes://tags** - Complete tag listing with creation timestamps +- **notes://stats** - Comprehensive database statistics and metrics +- **notes://config** - System configuration and capability information +- **notes://health** - Service health and availability status monitoring + +### Prompts (2 available) + +Pre-configured interaction templates: +- **search_notes** - Structured search interactions with flexible parameters +- **summarize_notes** - Generate analysis and summaries of note collections ### Starting the MCP Server ```bash # Start MCP server (for use with LLM clients) -ml-notes mcp +ml-notes-cli mcp # The server communicates via stdio for Claude Desktop integration +# Debug mode can be enabled with --debug flag +ml-notes-cli --debug mcp ``` +### Recent Enhancements (v1.1.0) + +The MCP server has been significantly enhanced with: + +- **Enhanced Search**: New `search_type` parameter with auto/vector/text/tags modes +- **Flexible Output**: `show_content` parameter for full content vs. previews +- **Better Validation**: Input validation with enums and parameter constraints +- **Rich Resources**: New URI template support for individual note access +- **Health Monitoring**: Comprehensive health and status checking +- **Improved Logging**: Detailed debug logging for troubleshooting +- **Smart Defaults**: Intelligent parameter defaults based on search type +- **Error Handling**: Enhanced error messages and validation + +**Compatibility**: Uses mcp-go v0.39.1 with latest MCP protocol support. + ## 🔧 Development ### Project Structure diff --git a/cmd/mcp.go b/cmd/mcp.go index bceb7f0..6546ba1 100644 --- a/cmd/mcp.go +++ b/cmd/mcp.go @@ -11,22 +11,44 @@ var mcpCmd = &cobra.Command{ Use: "mcp", Short: "Start MCP server for LLM integration", Long: `Start a Model Context Protocol (MCP) server that allows LLMs to interact with your notes. - -The MCP server provides tools and resources that enable LLMs to: -- Search notes using vector similarity or text search -- Add, update, and delete notes -- List and retrieve specific notes -- Access notes statistics and configuration + +The MCP server provides comprehensive tools and resources that enable LLMs to: + +Tools (10 available): +- add_note: Create new notes with optional tags +- search_notes: Enhanced search with vector/text/tag modes and flexible output +- get_note: Retrieve specific notes by ID +- list_notes: List notes with pagination +- update_note: Modify existing notes and tags +- delete_note: Remove notes from database +- list_tags: View all available tags +- update_note_tags: Manage note tags +- suggest_tags: AI-powered tag suggestions +- auto_tag_note: Automatically apply AI-generated tags + +Resources (6 available): +- notes://recent: Most recently created notes +- notes://note/{id}: Individual note access by ID +- notes://tags: Complete tag listing with metadata +- notes://stats: Comprehensive database statistics +- notes://config: System configuration and capabilities +- notes://health: Service health and availability status + +Prompts (2 available): +- search_notes: Structured search interactions +- summarize_notes: Generate analysis of note collections To use with Claude Desktop, add this to your claude_desktop_config.json: { "mcpServers": { "ml-notes": { - "command": "ml-notes", + "command": "ml-notes-cli", "args": ["mcp"] } } -}`, +} + +For CLI binary usage, ensure you're using 'ml-notes-cli' as the command.`, RunE: runMCP, } diff --git a/go.mod b/go.mod index 8024139..9fb11c4 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,11 @@ go 1.24 toolchain go1.24.7 require ( + github.com/JohannesKaufmann/html-to-markdown v1.6.0 + github.com/PuerkitoBio/goquery v1.9.2 + github.com/chromedp/chromedp v0.14.1 github.com/gorilla/mux v1.8.1 - github.com/mark3labs/mcp-go v0.37.0 + github.com/mark3labs/mcp-go v0.39.1 github.com/mattn/go-sqlite3 v1.14.22 github.com/rs/cors v1.11.1 github.com/spf13/cobra v1.8.0 @@ -14,14 +17,11 @@ require ( ) require ( - github.com/JohannesKaufmann/html-to-markdown v1.6.0 // indirect - github.com/PuerkitoBio/goquery v1.9.2 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 // indirect - github.com/chromedp/chromedp v0.14.1 // indirect github.com/chromedp/sysutil v1.1.0 // indirect github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect diff --git a/go.sum b/go.sum index 01ffc1c..d69c81d 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,8 @@ github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5 github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ= github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= -github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= -github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= -github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= -github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= @@ -40,8 +36,6 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -76,10 +70,12 @@ github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/ github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mark3labs/mcp-go v0.37.0 h1:BywvZLPRT6Zx6mMG/MJfxLSZQkTGIcJSEGKsvr4DsoQ= -github.com/mark3labs/mcp-go v0.37.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g= +github.com/mark3labs/mcp-go v0.39.1 h1:2oPxk7aDbQhouakkYyKl2T4hKFU1c6FDaubWyGyVE1k= +github.com/mark3labs/mcp-go v0.39.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= @@ -92,6 +88,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -109,9 +107,12 @@ github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= @@ -141,22 +142,17 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -164,22 +160,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -193,54 +181,40 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/cli/commands.go b/internal/cli/commands.go index 38442ff..d32b270 100644 --- a/internal/cli/commands.go +++ b/internal/cli/commands.go @@ -7,9 +7,11 @@ import ( "strconv" "strings" + "github.com/mark3labs/mcp-go/server" "github.com/spf13/cobra" "github.com/streed/ml-notes/internal/config" "github.com/streed/ml-notes/internal/logger" + "github.com/streed/ml-notes/internal/mcp" "github.com/streed/ml-notes/internal/models" ) @@ -27,6 +29,7 @@ func init() { rootCmd.AddCommand(analyzeCmd) rootCmd.AddCommand(autotagCmd) rootCmd.AddCommand(updateCmd) + rootCmd.AddCommand(mcpCmd) } // Init Command @@ -695,3 +698,69 @@ func init() { func runUpdate(cmd *cobra.Command, args []string) error { return runEdit(cmd, args) // Update is the same as edit } + +// MCP Command +var mcpCmd = &cobra.Command{ + Use: "mcp", + Short: "Start MCP server for LLM integration", + Long: `Start a Model Context Protocol (MCP) server that allows LLMs to interact with your notes. + +The MCP server provides comprehensive tools and resources that enable LLMs to: + +Tools (10 available): +- add_note: Create new notes with optional tags +- search_notes: Enhanced search with vector/text/tag modes and flexible output +- get_note: Retrieve specific notes by ID +- list_notes: List notes with pagination +- update_note: Modify existing notes and tags +- delete_note: Remove notes from database +- list_tags: View all available tags +- update_note_tags: Manage note tags +- suggest_tags: AI-powered tag suggestions +- auto_tag_note: Automatically apply AI-generated tags + +Resources (6 available): +- notes://recent: Most recently created notes +- notes://note/{id}: Individual note access by ID +- notes://tags: Complete tag listing with metadata +- notes://stats: Comprehensive database statistics +- notes://config: System configuration and capabilities +- notes://health: Service health and availability status + +Prompts (2 available): +- search_notes: Structured search interactions +- summarize_notes: Generate analysis of note collections + +To use with Claude Desktop, add this to your claude_desktop_config.json: +{ + "mcpServers": { + "ml-notes": { + "command": "ml-notes-cli", + "args": ["mcp"] + } + } +} + +For CLI binary usage, ensure you're using 'ml-notes-cli' as the command.`, + RunE: runMCP, +} + +func runMCP(cmd *cobra.Command, args []string) error { + logger.Info("Starting MCP server...") + + // Create MCP server + notesServer := mcp.NewNotesServer(appConfig, db.Conn(), noteRepo, vectorSearch) + mcpServer := notesServer.GetMCPServer() + + // Start server with stdio transport + logger.Info("MCP server ready. Listening on stdio...") + if err := server.ServeStdio(mcpServer); err != nil { + if err.Error() != "EOF" { + logger.Error("MCP server error: %v", err) + return err + } + } + + logger.Info("MCP server shutting down") + return nil +} diff --git a/internal/mcp/server.go b/internal/mcp/server.go index cf2509c..255e3db 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -35,22 +35,23 @@ func NewNotesServer(cfg *config.Config, db *sql.DB, repo *models.NoteRepository, autoTagger: autotag.NewAutoTagger(cfg), } - // Create MCP server + // Create MCP server with enhanced capabilities ns.mcpServer = server.NewMCPServer( "ml-notes", - "1.0.0", + "1.1.0", // Updated version server.WithToolCapabilities(true), + server.WithResourceCapabilities(true, true), // subscriptions, listing + server.WithPromptCapabilities(true), ) - // Register tools + // Register tools, resources, and prompts ns.registerTools() - - // Register resources ns.registerResources() - - // Register prompts ns.registerPrompts() + logger.Info("MCP server initialized with %d tools, %d resources, and %d prompts", + len(ns.getToolNames()), len(ns.getResourceNames()), len(ns.getPromptNames())) + return ns } @@ -76,21 +77,25 @@ func (s *NotesServer) registerTools() { ) s.mcpServer.AddTool(addNoteTool, s.handleAddNote) - // Search notes tool + // Search notes tool with enhanced parameter validation searchTool := mcp.NewTool("search_notes", mcp.WithDescription("Search for notes using vector similarity, text search, or tag search. Vector search returns the single most similar note by default."), mcp.WithString("query", mcp.Description("Search query string (optional if tags are provided)"), ), mcp.WithNumber("limit", - mcp.Description("Maximum number of results (default: 1 for vector, 10 for text/tags)"), + mcp.Description("Maximum number of results (default: 1 for vector, 10 for text/tags, max: 100)"), ), - mcp.WithBoolean("use_vector", - mcp.Description("Use vector search if available (default: true)"), + mcp.WithString("search_type", + mcp.Description("Type of search to perform"), + mcp.Enum("auto", "vector", "text", "tags"), ), mcp.WithString("tags", mcp.Description("Comma-separated tags to search for (optional)"), ), + mcp.WithBoolean("show_content", + mcp.Description("Include full content in results (default: false for summaries)"), + ), ) s.mcpServer.AddTool(searchTool, s.handleSearchNotes) @@ -193,25 +198,49 @@ func (s *NotesServer) registerResources() { recentResource := mcp.NewResource("notes://recent", "Recent Notes", mcp.WithResourceDescription("Get the most recently created notes"), - mcp.WithMIMEType("application/json"), + mcp.WithMIMEType("text/plain"), ) s.mcpServer.AddResource(recentResource, s.handleRecentNotes) + // Individual note resource with URI template + noteResource := mcp.NewResource("notes://note/{id}", + "Individual Note", + mcp.WithResourceDescription("Get a specific note by ID"), + mcp.WithMIMEType("text/plain"), + ) + s.mcpServer.AddResource(noteResource, s.handleNoteResource) + + // All tags resource + tagsResource := mcp.NewResource("notes://tags", + "All Tags", + mcp.WithResourceDescription("List all tags in the system with usage counts"), + mcp.WithMIMEType("text/plain"), + ) + s.mcpServer.AddResource(tagsResource, s.handleTagsResource) + // Stats resource statsResource := mcp.NewResource("notes://stats", "Notes Statistics", - mcp.WithResourceDescription("Get statistics about the notes database"), - mcp.WithMIMEType("application/json"), + mcp.WithResourceDescription("Get comprehensive statistics about the notes database"), + mcp.WithMIMEType("text/plain"), ) s.mcpServer.AddResource(statsResource, s.handleStats) // Config resource configResource := mcp.NewResource("notes://config", "Configuration", - mcp.WithResourceDescription("Get current ml-notes configuration"), - mcp.WithMIMEType("application/json"), + mcp.WithResourceDescription("Get current ml-notes configuration and capabilities"), + mcp.WithMIMEType("text/plain"), ) s.mcpServer.AddResource(configResource, s.handleConfig) + + // Health resource + healthResource := mcp.NewResource("notes://health", + "System Health", + mcp.WithResourceDescription("Get system health and service availability status"), + mcp.WithMIMEType("text/plain"), + ) + s.mcpServer.AddResource(healthResource, s.handleHealth) } func (s *NotesServer) registerPrompts() { @@ -299,26 +328,75 @@ func (s *NotesServer) handleSearchNotes(_ context.Context, request mcp.CallToolR query := request.GetString("query", "") tagsStr := request.GetString("tags", "") + searchType := request.GetString("search_type", "auto") + showContent := request.GetBool("show_content", false) // At least one of query or tags must be provided if query == "" && tagsStr == "" { return nil, fmt.Errorf("at least one of 'query' or 'tags' parameters must be provided") } - // Default limit: 1 for vector search, 10 for text search - useVector := request.GetBool("use_vector", true) - defaultLimit := 10 - if useVector && s.vectorSearch != nil { - defaultLimit = 1 // Vector search defaults to top result only + // Validate and set limit (max 100) + limit := request.GetInt("limit", 0) + if limit <= 0 { + // Smart defaults based on search type + switch searchType { + case "vector": + limit = 1 + case "tags": + limit = 10 + default: + if s.vectorSearch != nil && query != "" { + limit = 1 // Vector search default + } else { + limit = 10 // Text search default + } + } + } + if limit > 100 { + limit = 100 } - limit := request.GetInt("limit", defaultLimit) var notes []*models.Note var err error + var searchMethod string - // Handle tag search - if tagsStr != "" { - // Parse tags + // Determine search method + switch searchType { + case "tags": + if tagsStr == "" { + return nil, fmt.Errorf("tags parameter required for tag search") + } + searchMethod = "tag" + case "vector": + if s.vectorSearch == nil { + return nil, fmt.Errorf("vector search not available") + } + if query == "" { + return nil, fmt.Errorf("query parameter required for vector search") + } + searchMethod = "vector" + case "text": + if query == "" { + return nil, fmt.Errorf("query parameter required for text search") + } + searchMethod = "text" + case "auto": + // Smart search type selection + if tagsStr != "" { + searchMethod = "tag" + } else if s.vectorSearch != nil && query != "" { + searchMethod = "vector" + } else if query != "" { + searchMethod = "text" + } else { + return nil, fmt.Errorf("unable to determine search method") + } + } + + // Execute search + switch searchMethod { + case "tag": var tags []string for _, tag := range strings.Split(tagsStr, ",") { cleanTag := strings.TrimSpace(tag) @@ -327,17 +405,20 @@ func (s *NotesServer) handleSearchNotes(_ context.Context, request mcp.CallToolR } } notes, err = s.repo.SearchByTags(tags) - } else if useVector && s.vectorSearch != nil { + logger.Debug("Tag search for %v returned %d results", tags, len(notes)) + case "vector": notes, err = s.vectorSearch.SearchSimilar(query, limit) - } else { + logger.Debug("Vector search for '%s' returned %d results", query, len(notes)) + case "text": notes, err = s.repo.Search(query) + logger.Debug("Text search for '%s' returned %d results", query, len(notes)) } if err != nil { - return nil, fmt.Errorf("search failed: %w", err) + return nil, fmt.Errorf("search failed (%s): %w", searchMethod, err) } - // Limit results if text search returned too many + // Apply limit if needed if len(notes) > limit { notes = notes[:limit] } @@ -345,23 +426,26 @@ func (s *NotesServer) handleSearchNotes(_ context.Context, request mcp.CallToolR // Format results var result string if len(notes) == 0 { - result = "No notes found matching your query." - } else if len(notes) == 1 { - // Special formatting for single result (common with vector search) - note := notes[0] - result = fmt.Sprintf("Most similar note:\n\n[ID: %d] %s\n\n%s", - note.ID, note.Title, - truncateString(note.Content, 200)) // Show more content for single result + result = fmt.Sprintf("No notes found matching your query using %s search.", searchMethod) } else { - result = fmt.Sprintf("Found %d notes:\n\n", len(notes)) + result = fmt.Sprintf("Found %d notes using %s search:\n\n", len(notes), searchMethod) + for i, note := range notes { tagsInfo := "" if len(note.Tags) > 0 { tagsInfo = fmt.Sprintf(" [Tags: %s]", strings.Join(note.Tags, ", ")) } - result += fmt.Sprintf("%d. [ID: %d] %s%s\n %s\n\n", + + contentPreview := "" + if showContent { + contentPreview = fmt.Sprintf("\n Content: %s", note.Content) + } else { + contentPreview = fmt.Sprintf("\n Preview: %s", truncateString(note.Content, 150)) + } + + result += fmt.Sprintf("%d. [ID: %d] %s%s\n Created: %s%s\n\n", i+1, note.ID, note.Title, tagsInfo, - truncateString(note.Content, 100)) + note.CreatedAt.Format("2006-01-02"), contentPreview) } } @@ -559,13 +643,187 @@ func (s *NotesServer) handleStats(_ context.Context, request mcp.ReadResourceReq func (s *NotesServer) handleConfig(_ context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { logger.Debug("MCP resource read: notes://config") + vectorSearchStatus := "disabled" + if s.vectorSearch != nil { + vectorSearchStatus = "enabled" + } + + autoTagStatus := "disabled" + if s.autoTagger.IsAvailable() { + autoTagStatus = "enabled" + } + content := fmt.Sprintf(`ML Notes Configuration: + +General Settings: - Debug Mode: %v - Data Directory: %s -- Lil-rag URL: %s`, +- Database Path: %s + +AI Services: +- Lil-rag URL: %s +- Vector Search: %s +- Auto-tagging: %s +- Ollama Endpoint: %s + +MCP Server Capabilities: +- Tools: %d registered +- Resources: %d registered +- Prompts: %d registered +- Server Version: 1.1.0`, s.cfg.Debug, s.cfg.DataDirectory, - s.cfg.LilRagURL) + s.cfg.GetDatabasePath(), + s.cfg.LilRagURL, + vectorSearchStatus, + autoTagStatus, + s.cfg.OllamaEndpoint, + len(s.getToolNames()), + len(s.getResourceNames()), + len(s.getPromptNames())) + + return []mcp.ResourceContents{ + &mcp.TextResourceContents{ + Text: content, + }, + }, nil +} + +// New resource handlers +func (s *NotesServer) handleNoteResource(_ context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { + logger.Debug("MCP resource read: notes://note/{id}") + + // Extract ID from URI path + uri := request.Params.URI + parts := strings.Split(uri, "/") + if len(parts) < 3 { + return nil, fmt.Errorf("invalid note URI format") + } + + idStr := parts[len(parts)-1] + var noteID int + n, err := fmt.Sscanf(idStr, "%d", ¬eID) + if err != nil || n != 1 { + return nil, fmt.Errorf("invalid note ID: %s", idStr) + } + + note, err := s.repo.GetByID(noteID) + if err != nil { + return nil, fmt.Errorf("failed to get note %d: %w", noteID, err) + } + + tagsInfo := "" + if len(note.Tags) > 0 { + tagsInfo = fmt.Sprintf("\nTags: %s", strings.Join(note.Tags, ", ")) + } + + content := fmt.Sprintf(`Note: %s +ID: %d%s +Created: %s +Updated: %s + +%s`, + note.Title, + note.ID, + tagsInfo, + note.CreatedAt.Format("2006-01-02 15:04:05"), + note.UpdatedAt.Format("2006-01-02 15:04:05"), + note.Content) + + return []mcp.ResourceContents{ + &mcp.TextResourceContents{ + Text: content, + }, + }, nil +} + +func (s *NotesServer) handleTagsResource(_ context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { + logger.Debug("MCP resource read: notes://tags") + + tags, err := s.repo.GetAllTags() + if err != nil { + return nil, fmt.Errorf("failed to get tags: %w", err) + } + + var content string + if len(tags) == 0 { + content = "No tags found in the system." + } else { + content = fmt.Sprintf("All Tags (%d total):\n\n", len(tags)) + for i, tag := range tags { + content += fmt.Sprintf("%d. %s (Created: %s)\n", + i+1, tag.Name, tag.CreatedAt.Format("2006-01-02")) + } + } + + return []mcp.ResourceContents{ + &mcp.TextResourceContents{ + Text: content, + }, + }, nil +} + +func (s *NotesServer) handleHealth(_ context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { + logger.Debug("MCP resource read: notes://health") + + // Check database health + var dbStatus string + err := s.db.Ping() + if err != nil { + dbStatus = fmt.Sprintf("ERROR: %v", err) + } else { + dbStatus = "OK" + } + + // Check vector search health + var vectorStatus string + if s.vectorSearch == nil { + vectorStatus = "DISABLED" + } else { + // Try a simple search to test connectivity + _, err := s.vectorSearch.SearchSimilar("test", 1) + if err != nil { + vectorStatus = fmt.Sprintf("ERROR: %v", err) + } else { + vectorStatus = "OK" + } + } + + // Check auto-tagger health + var autoTagStatus string + if !s.autoTagger.IsAvailable() { + autoTagStatus = "DISABLED" + } else { + autoTagStatus = "OK" + } + + // Get note count + var noteCount int + err = s.db.QueryRow("SELECT COUNT(*) FROM notes").Scan(¬eCount) + if err != nil { + noteCount = 0 // Default to 0 if query fails + logger.Error("Failed to get note count: %v", err) + } + + content := fmt.Sprintf(`System Health Status: + +Database: %s +Vector Search: %s +Auto-tagging: %s + +Statistics: +- Total Notes: %d +- Working Directory: %s +- Project Namespace: %s + +Last Updated: %s`, + dbStatus, + vectorStatus, + autoTagStatus, + noteCount, + s.getCurrentProjectNamespace(), + s.getCurrentProjectNamespace(), + fmt.Sprintf("%v", context.Background().Value("timestamp"))) return []mcp.ResourceContents{ &mcp.TextResourceContents{ @@ -800,7 +1058,28 @@ func truncateString(s string, maxLen int) string { func (s *NotesServer) getCurrentProjectNamespace() string { cwd, err := os.Getwd() if err != nil { - return "" + return "unknown" } return filepath.Base(cwd) } + +// Helper methods for tracking capabilities +func (s *NotesServer) getToolNames() []string { + return []string{ + "add_note", "search_notes", "get_note", "list_notes", "update_note", "delete_note", + "list_tags", "update_note_tags", "suggest_tags", "auto_tag_note", + } +} + +func (s *NotesServer) getResourceNames() []string { + return []string{ + "notes://recent", "notes://note/{id}", "notes://tags", "notes://stats", + "notes://config", "notes://health", + } +} + +func (s *NotesServer) getPromptNames() []string { + return []string{ + "search_notes", "summarize_notes", + } +} diff --git a/test-build b/test-build new file mode 100755 index 0000000..7cc8ed4 Binary files /dev/null and b/test-build differ