Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
stats/
.env.bak
*cookies*txt
cookies*
Expand Down
44 changes: 44 additions & 0 deletions mcp-servers/go/pandoc-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Build stage
FROM --platform=$TARGETPLATFORM golang:1.23 AS builder
WORKDIR /src

# Copy go mod files first for better caching
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Build with optimizations
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-s -w" -o /pandoc-server .

# Pandoc stage - extract pandoc and its dependencies
FROM debian:stable-slim as pandoc_stage
RUN apt-get update && \
apt-get install -y --no-install-recommends pandoc && \
rm -rf /var/lib/apt/lists/*

# Final stage - minimal runtime
FROM debian:stable-slim

# Install runtime dependencies for pandoc
RUN apt-get update && \
apt-get install -y --no-install-recommends libgmp10 && \
rm -rf /var/lib/apt/lists/* && \
# Create non-root user
useradd -m -u 1000 -s /bin/bash mcp

# Copy binaries
COPY --from=builder --chown=mcp:mcp /pandoc-server /usr/local/bin/pandoc-server
COPY --from=pandoc_stage /usr/bin/pandoc /usr/bin/pandoc

# Switch to non-root user
USER mcp
WORKDIR /home/mcp

# Add metadata
LABEL org.opencontainers.image.title="Pandoc MCP Server" \
org.opencontainers.image.description="MCP server for pandoc document conversion" \
org.opencontainers.image.version="0.2.0"

ENTRYPOINT ["/usr/local/bin/pandoc-server"]
274 changes: 274 additions & 0 deletions mcp-servers/go/pandoc-server/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 📄 PANDOC-SERVER - Makefile
# MCP server for pandoc document conversion
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#
# Author : Mihai Criveti
# Usage : make <target> or just `make help`
#
# help: 📄 PANDOC-SERVER (Go MCP server for document conversion)
# ─────────────────────────────────────────────────────────────────────────

# =============================================================================
# 📖 DYNAMIC HELP
# =============================================================================
.PHONY: help
help:
@grep '^# help\:' $(firstword $(MAKEFILE_LIST)) | sed 's/^# help\: //'

# =============================================================================
# 📦 PROJECT METADATA
# =============================================================================
MODULE := pandoc-server
BIN_NAME := pandoc-server
VERSION ?= $(shell git describe --tags --dirty --always 2>/dev/null || echo "v0.2.0")

DIST_DIR := dist
COVERPROFILE := $(DIST_DIR)/coverage.out
COVERHTML := $(DIST_DIR)/coverage.html

GO ?= go
GOOS ?= $(shell $(GO) env GOOS)
GOARCH ?= $(shell $(GO) env GOARCH)

LDFLAGS := -s -w -X 'main.appVersion=$(VERSION)'

ifeq ($(shell test -t 1 && echo tty),tty)
C_GREEN := \033[38;5;82m
C_BLUE := \033[38;5;75m
C_RESET := \033[0m
else
C_GREEN :=
C_BLUE :=
C_RESET :=
endif

# =============================================================================
# 🔧 TOOLING
# =============================================================================
# help: 🔧 TOOLING
# help: tools - Install golangci-lint & staticcheck

GOBIN := $(shell $(GO) env GOPATH)/bin

.PHONY: tools
tools: $(GOBIN)/golangci-lint $(GOBIN)/staticcheck

$(GOBIN)/golangci-lint:
@echo "$(C_BLUE)Installing golangci-lint...$(C_RESET)"
@$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

$(GOBIN)/staticcheck:
@echo "$(C_BLUE)Installing staticcheck...$(C_RESET)"
@$(GO) install honnef.co/go/tools/cmd/staticcheck@latest

# =============================================================================
# 📂 MODULE & FORMAT
# =============================================================================
# help: 📂 MODULE & FORMAT
# help: tidy - go mod tidy + verify
# help: fmt - Run gofmt & goimports

.PHONY: tidy fmt

tidy:
@echo "$(C_BLUE)Tidying dependencies...$(C_RESET)"
@$(GO) mod tidy
@$(GO) mod verify

fmt:
@echo "$(C_BLUE)Formatting code...$(C_RESET)"
@$(GO) fmt ./...
@go run golang.org/x/tools/cmd/goimports@latest -w .

# =============================================================================
# 🔍 LINTING & STATIC ANALYSIS
# =============================================================================
# help: 🔍 LINTING & STATIC ANALYSIS
# help: vet - go vet
# help: staticcheck - Run staticcheck
# help: lint - Run golangci-lint
# help: pre-commit - Run all pre-commit hooks

.PHONY: vet staticcheck lint pre-commit

vet:
@echo "$(C_BLUE)Running go vet...$(C_RESET)"
@$(GO) vet ./...

staticcheck: tools
@echo "$(C_BLUE)Running staticcheck...$(C_RESET)"
@staticcheck ./...

lint: tools
@echo "$(C_BLUE)Running golangci-lint...$(C_RESET)"
@golangci-lint run

pre-commit:
@command -v pre-commit >/dev/null 2>&1 || { \
echo '✖ pre-commit not installed → pip install --user pre-commit'; exit 1; }
@pre-commit run --all-files --show-diff-on-failure

# =============================================================================
# 🧪 TESTS & COVERAGE
# =============================================================================
# help: 🧪 TESTS & COVERAGE
# help: test - Run unit tests (race)
# help: test-verbose - Run tests with verbose output
# help: coverage - Generate HTML coverage report
# help: test-integration - Run integration tests

.PHONY: test test-verbose coverage test-integration

test:
@echo "$(C_BLUE)Running tests...$(C_RESET)"
@$(GO) test -race -timeout=90s ./...

test-verbose:
@echo "$(C_BLUE)Running tests (verbose)...$(C_RESET)"
@$(GO) test -v -race -timeout=90s ./...

coverage:
@mkdir -p $(DIST_DIR)
@echo "$(C_BLUE)Generating coverage report...$(C_RESET)"
@$(GO) test ./... -covermode=count -coverprofile=$(COVERPROFILE)
@$(GO) tool cover -html=$(COVERPROFILE) -o $(COVERHTML)
@echo "$(C_GREEN)✔ HTML coverage → $(COVERHTML)$(C_RESET)"

test-integration: build
@echo "$(C_BLUE)Running integration tests...$(C_RESET)"
@./test_integration.sh

# =============================================================================
# 🛠 BUILD & RUN
# =============================================================================
# help: 🛠 BUILD & RUN
# help: build - Build binary into ./dist
# help: install - go install into GOPATH/bin
# help: release - Cross-compile (honours GOOS/GOARCH)
# help: run - Build then run (stdio transport)
# help: run-translate - Run with MCP Gateway translate on :9000

.PHONY: build install release run run-translate

build: tidy
@mkdir -p $(DIST_DIR)
@echo "$(C_BLUE)Building $(BIN_NAME)...$(C_RESET)"
@CGO_ENABLED=0 $(GO) build -trimpath -ldflags '$(LDFLAGS)' -o $(DIST_DIR)/$(BIN_NAME) .
@echo "$(C_GREEN)✔ Built → $(DIST_DIR)/$(BIN_NAME)$(C_RESET)"

install:
@echo "$(C_BLUE)Installing $(BIN_NAME)...$(C_RESET)"
@$(GO) install -trimpath -ldflags '$(LDFLAGS)' .
@echo "$(C_GREEN)✔ Installed → $(GOBIN)/$(BIN_NAME)$(C_RESET)"

release:
@mkdir -p $(DIST_DIR)/$(GOOS)-$(GOARCH)
@echo "$(C_BLUE)Building release for $(GOOS)/$(GOARCH)...$(C_RESET)"
@GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 \
$(GO) build -trimpath -ldflags '$(LDFLAGS)' \
-o $(DIST_DIR)/$(GOOS)-$(GOARCH)/$(BIN_NAME) .
@echo "$(C_GREEN)✔ Release → $(DIST_DIR)/$(GOOS)-$(GOARCH)/$(BIN_NAME)$(C_RESET)"

run: build
@echo "$(C_BLUE)Starting $(BIN_NAME) (stdio)...$(C_RESET)"
@$(DIST_DIR)/$(BIN_NAME)

run-translate: build
@echo "$(C_BLUE)Starting $(BIN_NAME) with MCP Gateway on :9000...$(C_RESET)"
@python3 -m mcpgateway.translate --stdio "$(DIST_DIR)/$(BIN_NAME)" --port 9000

# =============================================================================
# 🐳 DOCKER
# =============================================================================
# help: 🐳 DOCKER
# help: docker-build - Build container image
# help: docker-run - Run container (stdio)
# help: docker-test - Test container with pandoc conversion

IMAGE ?= $(BIN_NAME):$(VERSION)

.PHONY: docker-build docker-run docker-test

docker-build:
@echo "$(C_BLUE)Building Docker image $(IMAGE)...$(C_RESET)"
@docker build --build-arg VERSION=$(VERSION) -t $(IMAGE) .
@docker images $(IMAGE)
@echo "$(C_GREEN)✔ Docker image → $(IMAGE)$(C_RESET)"

docker-run: docker-build
@echo "$(C_BLUE)Running Docker container...$(C_RESET)"
@docker run --rm -i $(IMAGE)

docker-test: docker-build
@echo "$(C_BLUE)Testing Docker container...$(C_RESET)"
@echo '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}' | \
docker run --rm -i $(IMAGE) | python3 -m json.tool

# =============================================================================
# 📚 PANDOC SPECIFIC TESTS
# =============================================================================
# help: 📚 PANDOC TESTS
# help: test-pandoc - Test pandoc conversion
# help: test-formats - Test listing formats
# help: test-health - Test health check

.PHONY: test-pandoc test-formats test-health

test-pandoc: build
@echo "$(C_BLUE)Testing pandoc conversion...$(C_RESET)"
@echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"pandoc","arguments":{"from":"markdown","to":"html","input":"# Hello\\n\\nThis is **bold** text."}},"id":1}' | \
timeout 2 $(DIST_DIR)/$(BIN_NAME) 2>/dev/null | python3 -m json.tool | head -20

test-formats: build
@echo "$(C_BLUE)Testing format listing...$(C_RESET)"
@echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"list-formats","arguments":{"type":"input"}},"id":1}' | \
timeout 2 $(DIST_DIR)/$(BIN_NAME) 2>/dev/null | python3 -c "import json, sys; d=json.loads(sys.stdin.read()); print('Input formats:', len(d['result']['content'][0]['text'].split()))"

test-health: build
@echo "$(C_BLUE)Testing health check...$(C_RESET)"
@echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"health","arguments":{}},"id":1}' | \
timeout 2 $(DIST_DIR)/$(BIN_NAME) 2>/dev/null | python3 -c "import json, sys; d=json.loads(sys.stdin.read()); print('pandoc' in d['result']['content'][0]['text'] and '✔ Health check passed' or '✖ Health check failed')"

# =============================================================================
# 🧹 CLEANUP
# =============================================================================
# help: 🧹 CLEANUP
# help: clean - Remove build & coverage artifacts
# help: clean-all - Clean + remove tool binaries

.PHONY: clean clean-all

clean:
@echo "$(C_BLUE)Cleaning build artifacts...$(C_RESET)"
@rm -rf $(DIST_DIR) $(COVERPROFILE) $(COVERHTML)
@echo "$(C_GREEN)✔ Workspace clean$(C_RESET)"

clean-all: clean
@echo "$(C_BLUE)Removing tool binaries...$(C_RESET)"
@rm -f $(GOBIN)/golangci-lint $(GOBIN)/staticcheck
@echo "$(C_GREEN)✔ All clean$(C_RESET)"

# =============================================================================
# 🚀 QUICK DEVELOPMENT
# =============================================================================
# help: 🚀 QUICK DEVELOPMENT
# help: dev - Format, test, and build
# help: check - Run all checks (vet, lint, test)
# help: all - Full pipeline (fmt, check, build, test-pandoc)

.PHONY: dev check all

dev: fmt test build
@echo "$(C_GREEN)✔ Development build complete$(C_RESET)"

check: vet lint test
@echo "$(C_GREEN)✔ All checks passed$(C_RESET)"

all: fmt check build test-pandoc test-formats test-health
@echo "$(C_GREEN)✔ Full pipeline complete$(C_RESET)"

# ---------------------------------------------------------------------------
# Default goal
# ---------------------------------------------------------------------------
.DEFAULT_GOAL := help
Loading
Loading