diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af1d059a2..1fa068ebe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -482,8 +482,8 @@ repos: - id: remove-tabs name: ๐Ÿš€ Remove Tabs description: Replaces tabs by whitespaces. - # exclude root-level Makefiles and any path ending in /Makefile or .mk - exclude: '(^|/)Makefile$|\.mk$' + # exclude root-level Makefiles and any path ending in /Makefile or .mk or .jinja + exclude: '(^|/)Makefile$|(\.mk|\.jinja)$' # ----------------------------------------------------------------------------- # ๐Ÿšซ Branch Protection Hooks diff --git a/docs/docs/using/plugins/lifecycle.md b/docs/docs/using/plugins/lifecycle.md index 4b6829ab8..a44029ecd 100644 --- a/docs/docs/using/plugins/lifecycle.md +++ b/docs/docs/using/plugins/lifecycle.md @@ -71,7 +71,17 @@ Inspect those two files and get familiar with them. There are many options you c ### Dependencies -Plugins are Python packages with dependencies managed by `uv`. Just like the gateway, you can add, lock, lint, and ensure that best practices are followed when creating your plugins. To ensure compatibility with the gateway linters, we do not directly install them in the template. Instead, please install the gateway package with the dev extras, which will include all required linters. +Plugins are Python packages with dependencies managed by `uv`. Just like the gateway, you can add, lock, lint, and ensure that best practices are followed when creating your plugins. To install dependencies with dev packages (required for linting and testing), run: + +```bash +make install-dev +``` + +Alternatively, you can also install it in editable mode: + +```bash +make install-editable +``` ### Test diff --git a/mcpgateway/plugins/tools/cli.py b/mcpgateway/plugins/tools/cli.py index 75e921cdd..efe3e84d5 100644 --- a/mcpgateway/plugins/tools/cli.py +++ b/mcpgateway/plugins/tools/cli.py @@ -45,9 +45,7 @@ # --------------------------------------------------------------------------- # Configuration defaults # --------------------------------------------------------------------------- -DEFAULT_TEMPLATE_BASE_URL = "https://github.com/IBM/mcp-context-forge.git" -DEFAULT_TEMPLATE_TYPE = "external" -DEFAULT_TEMPLATE_URL = f"{DEFAULT_TEMPLATE_BASE_URL}::plugin_templates/{DEFAULT_TEMPLATE_TYPE}" +DEFAULT_TEMPLATE_URL = "https://github.com/IBM/mcp-context-forge.git" DEFAULT_AUTHOR_NAME = "" DEFAULT_AUTHOR_EMAIL = "" DEFAULT_PROJECT_DIR = Path("./.") diff --git a/plugin_templates/external/Makefile.jinja b/plugin_templates/external/Makefile.jinja index b864d2fee..960a842d8 100644 --- a/plugin_templates/external/Makefile.jinja +++ b/plugin_templates/external/Makefile.jinja @@ -18,22 +18,22 @@ VENV_DIR ?= $(VENVS_DIR)/$(PROJECT_NAME) # ============================================================================= black: - @echo "๐ŸŽจ black $(TARGET)..." && $(VENV_DIR)/bin/black -l 200 $(TARGET) + @echo "๐ŸŽจ black $(TARGET)..." && $(VENV_DIR)/bin/black -l 200 $(TARGET) black-check: - @echo "๐ŸŽจ black --check $(TARGET)..." && $(VENV_DIR)/bin/black -l 200 --check --diff $(TARGET) + @echo "๐ŸŽจ black --check $(TARGET)..." && $(VENV_DIR)/bin/black -l 200 --check --diff $(TARGET) ruff: - @echo "โšก ruff $(TARGET)..." && $(VENV_DIR)/bin/ruff check $(TARGET) && $(VENV_DIR)/bin/ruff format $(TARGET) + @echo "โšก ruff $(TARGET)..." && $(VENV_DIR)/bin/ruff check $(TARGET) && $(VENV_DIR)/bin/ruff format $(TARGET) ruff-check: - @echo "โšก ruff check $(TARGET)..." && $(VENV_DIR)/bin/ruff check $(TARGET) + @echo "โšก ruff check $(TARGET)..." && $(VENV_DIR)/bin/ruff check $(TARGET) ruff-fix: - @echo "โšก ruff check --fix $(TARGET)..." && $(VENV_DIR)/bin/ruff check --fix $(TARGET) + @echo "โšก ruff check --fix $(TARGET)..." && $(VENV_DIR)/bin/ruff check --fix $(TARGET) ruff-format: - @echo "โšก ruff format $(TARGET)..." && $(VENV_DIR)/bin/ruff format $(TARGET) + @echo "โšก ruff format $(TARGET)..." && $(VENV_DIR)/bin/ruff format $(TARGET) # ============================================================================= # Container runtime configuration and operations @@ -54,7 +54,7 @@ CONTAINER_PORT ?= 8000 CONTAINER_INTERNAL_PORT ?= 8000 print-runtime: - @echo Using container runtime: $(CONTAINER_RUNTIME) + @echo Using container runtime: $(CONTAINER_RUNTIME) # Base image name (without any prefix) IMAGE_BASE ?= mcpgateway/$(PROJECT_NAME) @@ -74,10 +74,10 @@ else endif print-image: - @echo "๐Ÿณ Container Runtime: $(CONTAINER_RUNTIME)" - @echo "Using image: $(IMAGE_LOCAL)" - @echo "Development image: $(IMAGE_LOCAL_DEV)" - @echo "Push image: $(IMAGE_PUSH)" + @echo "๐Ÿณ Container Runtime: $(CONTAINER_RUNTIME)" + @echo "Using image: $(IMAGE_LOCAL)" + @echo "Development image: $(IMAGE_LOCAL_DEV)" + @echo "Push image: $(IMAGE_PUSH)" {% raw %} @@ -99,195 +99,195 @@ CONTAINER_FILE ?= $(shell [ -f "Containerfile" ] && echo "Containerfile" || echo COMMA := , container-info: - @echo "๐Ÿณ Container Runtime Configuration" - @echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - @echo "Runtime: $(CONTAINER_RUNTIME)" - @echo "Base Image: $(IMAGE_BASE)" - @echo "Tag: $(IMAGE_TAG)" - @echo "Local Image: $(IMAGE_LOCAL)" - @echo "Push Image: $(IMAGE_PUSH)" - @echo "Actual Image: $(call get_image_name)" - @echo "Container File: $(CONTAINER_FILE)" - @echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + @echo "๐Ÿณ Container Runtime Configuration" + @echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + @echo "Runtime: $(CONTAINER_RUNTIME)" + @echo "Base Image: $(IMAGE_BASE)" + @echo "Tag: $(IMAGE_TAG)" + @echo "Local Image: $(IMAGE_LOCAL)" + @echo "Push Image: $(IMAGE_PUSH)" + @echo "Actual Image: $(call get_image_name)" + @echo "Container File: $(CONTAINER_FILE)" + @echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" # Auto-detect platform based on uname PLATFORM ?= linux/$(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') container-build: - @echo "๐Ÿ”จ Building with $(CONTAINER_RUNTIME) for platform $(PLATFORM)..." - $(CONTAINER_RUNTIME) build \ - --platform=$(PLATFORM) \ - -f $(CONTAINER_FILE) \ - --tag $(IMAGE_BASE):$(IMAGE_TAG) \ - . - @echo "โœ… Built image: $(call get_image_name)" - $(CONTAINER_RUNTIME) images $(IMAGE_BASE):$(IMAGE_TAG) + @echo "๐Ÿ”จ Building with $(CONTAINER_RUNTIME) for platform $(PLATFORM)..." + $(CONTAINER_RUNTIME) build \ + --platform=$(PLATFORM) \ + -f $(CONTAINER_FILE) \ + --tag $(IMAGE_BASE):$(IMAGE_TAG) \ + . + @echo "โœ… Built image: $(call get_image_name)" + $(CONTAINER_RUNTIME) images $(IMAGE_BASE):$(IMAGE_TAG) container-run: container-check-image - @echo "๐Ÿš€ Running with $(CONTAINER_RUNTIME)..." - -$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true - -$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true - $(CONTAINER_RUNTIME) run --name $(PROJECT_NAME) \ - --env-file=.env \ - -p $(CONTAINER_PORT):$(CONTAINER_INTERNAL_PORT) \ - --restart=always \ - --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ - --health-cmd="curl --fail http://localhost:$(CONTAINER_INTERNAL_PORT)/health || exit 1" \ - --health-interval=1m --health-retries=3 \ - --health-start-period=30s --health-timeout=10s \ - -d $(call get_image_name) - @sleep 2 - @echo "โœ… Container started" - @echo "๐Ÿ” Health check status:" - @$(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{.State.Health.Status}}' 2>/dev/null || echo "No health check configured" + @echo "๐Ÿš€ Running with $(CONTAINER_RUNTIME)..." + -$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true + -$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true + $(CONTAINER_RUNTIME) run --name $(PROJECT_NAME) \ + --env-file=.env \ + -p $(CONTAINER_PORT):$(CONTAINER_INTERNAL_PORT) \ + --restart=always \ + --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ + --health-cmd="curl --fail http://localhost:$(CONTAINER_INTERNAL_PORT)/health || exit 1" \ + --health-interval=1m --health-retries=3 \ + --health-start-period=30s --health-timeout=10s \ + -d $(call get_image_name) + @sleep 2 + @echo "โœ… Container started" + @echo "๐Ÿ” Health check status:" + @$(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{.State.Health.Status}}' 2>/dev/null || echo "No health check configured" container-run-host: container-check-image - @echo "๐Ÿš€ Running with $(CONTAINER_RUNTIME)..." - -$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true - -$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true - $(CONTAINER_RUNTIME) run --name $(PROJECT_NAME) \ - --env-file=.env \ - --network=host \ - -p $(CONTAINER_PORT):$(CONTAINER_INTERNAL_PORT) \ - --restart=always \ - --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ - --health-cmd="curl --fail http://localhost:$(CONTAINER_INTERNAL_PORT)/health || exit 1" \ - --health-interval=1m --health-retries=3 \ - --health-start-period=30s --health-timeout=10s \ - -d $(call get_image_name) - @sleep 2 - @echo "โœ… Container started" - @echo "๐Ÿ” Health check status:" - @$(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{.State.Health.Status}}' 2>/dev/null || echo "No health check configured" + @echo "๐Ÿš€ Running with $(CONTAINER_RUNTIME)..." + -$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true + -$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true + $(CONTAINER_RUNTIME) run --name $(PROJECT_NAME) \ + --env-file=.env \ + --network=host \ + -p $(CONTAINER_PORT):$(CONTAINER_INTERNAL_PORT) \ + --restart=always \ + --memory=$(CONTAINER_MEMORY) --cpus=$(CONTAINER_CPUS) \ + --health-cmd="curl --fail http://localhost:$(CONTAINER_INTERNAL_PORT)/health || exit 1" \ + --health-interval=1m --health-retries=3 \ + --health-start-period=30s --health-timeout=10s \ + -d $(call get_image_name) + @sleep 2 + @echo "โœ… Container started" + @echo "๐Ÿ” Health check status:" + @$(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{.State.Health.Status}}' 2>/dev/null || echo "No health check configured" container-push: container-check-image - @echo "๐Ÿ“ค Preparing to push image..." - @# For Podman, we need to remove localhost/ prefix for push - @if [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ - actual_image=$$($(CONTAINER_RUNTIME) images --format "{{.Repository}}:{{.Tag}}" | grep -E "$(IMAGE_BASE):$(IMAGE_TAG)" | head -1); \ - if echo "$$actual_image" | grep -q "^localhost/"; then \ - echo "๐Ÿท๏ธ Tagging for push (removing localhost/ prefix)..."; \ - $(CONTAINER_RUNTIME) tag "$$actual_image" $(IMAGE_PUSH); \ - fi; \ - fi - $(CONTAINER_RUNTIME) push $(IMAGE_PUSH) - @echo "โœ… Pushed: $(IMAGE_PUSH)" + @echo "๐Ÿ“ค Preparing to push image..." + @# For Podman, we need to remove localhost/ prefix for push + @if [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ + actual_image=$$($(CONTAINER_RUNTIME) images --format "{{.Repository}}:{{.Tag}}" | grep -E "$(IMAGE_BASE):$(IMAGE_TAG)" | head -1); \ + if echo "$$actual_image" | grep -q "^localhost/"; then \ + echo "๐Ÿท๏ธ Tagging for push (removing localhost/ prefix)..."; \ + $(CONTAINER_RUNTIME) tag "$$actual_image" $(IMAGE_PUSH); \ + fi; \ + fi + $(CONTAINER_RUNTIME) push $(IMAGE_PUSH) + @echo "โœ… Pushed: $(IMAGE_PUSH)" container-check-image: - @echo "๐Ÿ” Checking for image..." - @if [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ - if ! $(CONTAINER_RUNTIME) image exists $(IMAGE_LOCAL) 2>/dev/null && \ - ! $(CONTAINER_RUNTIME) image exists $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null; then \ - echo "โŒ Image not found: $(IMAGE_LOCAL)"; \ - echo "๐Ÿ’ก Run 'make container-build' first"; \ - exit 1; \ - fi; \ - else \ - if ! $(CONTAINER_RUNTIME) images -q $(IMAGE_LOCAL) 2>/dev/null | grep -q . && \ - ! $(CONTAINER_RUNTIME) images -q $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null | grep -q .; then \ - echo "โŒ Image not found: $(IMAGE_LOCAL)"; \ - echo "๐Ÿ’ก Run 'make container-build' first"; \ - exit 1; \ - fi; \ - fi - @echo "โœ… Image found" + @echo "๐Ÿ” Checking for image..." + @if [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ + if ! $(CONTAINER_RUNTIME) image exists $(IMAGE_LOCAL) 2>/dev/null && \ + ! $(CONTAINER_RUNTIME) image exists $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null; then \ + echo "โŒ Image not found: $(IMAGE_LOCAL)"; \ + echo "๐Ÿ’ก Run 'make container-build' first"; \ + exit 1; \ + fi; \ + else \ + if ! $(CONTAINER_RUNTIME) images -q $(IMAGE_LOCAL) 2>/dev/null | grep -q . && \ + ! $(CONTAINER_RUNTIME) images -q $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null | grep -q .; then \ + echo "โŒ Image not found: $(IMAGE_LOCAL)"; \ + echo "๐Ÿ’ก Run 'make container-build' first"; \ + exit 1; \ + fi; \ + fi + @echo "โœ… Image found" container-stop: - @echo "๐Ÿ›‘ Stopping container..." - -$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true - -$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true - @echo "โœ… Container stopped and removed" + @echo "๐Ÿ›‘ Stopping container..." + -$(CONTAINER_RUNTIME) stop $(PROJECT_NAME) 2>/dev/null || true + -$(CONTAINER_RUNTIME) rm $(PROJECT_NAME) 2>/dev/null || true + @echo "โœ… Container stopped and removed" container-logs: - @echo "๐Ÿ“œ Streaming logs (Ctrl+C to exit)..." - $(CONTAINER_RUNTIME) logs -f $(PROJECT_NAME) + @echo "๐Ÿ“œ Streaming logs (Ctrl+C to exit)..." + $(CONTAINER_RUNTIME) logs -f $(PROJECT_NAME) container-shell: - @echo "๐Ÿ”ง Opening shell in container..." - @if ! $(CONTAINER_RUNTIME) ps -q -f name=$(PROJECT_NAME) | grep -q .; then \ - echo "โŒ Container $(PROJECT_NAME) is not running"; \ - echo "๐Ÿ’ก Run 'make container-run' first"; \ - exit 1; \ - fi - @$(CONTAINER_RUNTIME) exec -it $(PROJECT_NAME) /bin/bash 2>/dev/null || \ - $(CONTAINER_RUNTIME) exec -it $(PROJECT_NAME) /bin/sh + @echo "๐Ÿ”ง Opening shell in container..." + @if ! $(CONTAINER_RUNTIME) ps -q -f name=$(PROJECT_NAME) | grep -q .; then \ + echo "โŒ Container $(PROJECT_NAME) is not running"; \ + echo "๐Ÿ’ก Run 'make container-run' first"; \ + exit 1; \ + fi + @$(CONTAINER_RUNTIME) exec -it $(PROJECT_NAME) /bin/bash 2>/dev/null || \ + $(CONTAINER_RUNTIME) exec -it $(PROJECT_NAME) /bin/sh container-health: - @echo "๐Ÿฅ Checking container health..." - @if ! $(CONTAINER_RUNTIME) ps -q -f name=$(PROJECT_NAME) | grep -q .; then \ - echo "โŒ Container $(PROJECT_NAME) is not running"; \ - exit 1; \ - fi - @echo "Status: $$($(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{.State.Health.Status}}' 2>/dev/null || echo 'No health check')" - @echo "Logs:" - @$(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{range .State.Health.Log}}{{.Output}}{{end}}' 2>/dev/null || true + @echo "๐Ÿฅ Checking container health..." + @if ! $(CONTAINER_RUNTIME) ps -q -f name=$(PROJECT_NAME) | grep -q .; then \ + echo "โŒ Container $(PROJECT_NAME) is not running"; \ + exit 1; \ + fi + @echo "Status: $$($(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{.State.Health.Status}}' 2>/dev/null || echo 'No health check')" + @echo "Logs:" + @$(CONTAINER_RUNTIME) inspect $(PROJECT_NAME) --format='{{range .State.Health.Log}}{{.Output}}{{end}}' 2>/dev/null || true container-build-multi: - @echo "๐Ÿ”จ Building multi-architecture image..." - @if [ "$(CONTAINER_RUNTIME)" = "docker" ]; then \ - if ! docker buildx inspect $(PROJECT_NAME)-builder >/dev/null 2>&1; then \ - echo "๐Ÿ“ฆ Creating buildx builder..."; \ - docker buildx create --name $(PROJECT_NAME)-builder; \ - fi; \ - docker buildx use $(PROJECT_NAME)-builder; \ - docker buildx build \ - --platform=linux/amd64,linux/arm64 \ - -f $(CONTAINER_FILE) \ - --tag $(IMAGE_BASE):$(IMAGE_TAG) \ - --push \ - .; \ - elif [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ - echo "๐Ÿ“ฆ Building manifest with Podman..."; \ - $(CONTAINER_RUNTIME) build --platform=linux/amd64,linux/arm64 \ - -f $(CONTAINER_FILE) \ - --manifest $(IMAGE_BASE):$(IMAGE_TAG) \ - .; \ - echo "๐Ÿ’ก To push: podman manifest push $(IMAGE_BASE):$(IMAGE_TAG)"; \ - else \ - echo "โŒ Multi-arch builds require Docker buildx or Podman"; \ - exit 1; \ - fi + @echo "๐Ÿ”จ Building multi-architecture image..." + @if [ "$(CONTAINER_RUNTIME)" = "docker" ]; then \ + if ! docker buildx inspect $(PROJECT_NAME)-builder >/dev/null 2>&1; then \ + echo "๐Ÿ“ฆ Creating buildx builder..."; \ + docker buildx create --name $(PROJECT_NAME)-builder; \ + fi; \ + docker buildx use $(PROJECT_NAME)-builder; \ + docker buildx build \ + --platform=linux/amd64,linux/arm64 \ + -f $(CONTAINER_FILE) \ + --tag $(IMAGE_BASE):$(IMAGE_TAG) \ + --push \ + .; \ + elif [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ + echo "๐Ÿ“ฆ Building manifest with Podman..."; \ + $(CONTAINER_RUNTIME) build --platform=linux/amd64,linux/arm64 \ + -f $(CONTAINER_FILE) \ + --manifest $(IMAGE_BASE):$(IMAGE_TAG) \ + .; \ + echo "๐Ÿ’ก To push: podman manifest push $(IMAGE_BASE):$(IMAGE_TAG)"; \ + else \ + echo "โŒ Multi-arch builds require Docker buildx or Podman"; \ + exit 1; \ + fi # Helper targets for debugging image issues image-list: - @echo "๐Ÿ“‹ Images matching $(IMAGE_BASE):" - @$(CONTAINER_RUNTIME) images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Created}}\t{{.Size}}" | \ - grep -E "(IMAGE|$(IMAGE_BASE))" || echo "No matching images found" + @echo "๐Ÿ“‹ Images matching $(IMAGE_BASE):" + @$(CONTAINER_RUNTIME) images --format "table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Created}}\t{{.Size}}" | \ + grep -E "(IMAGE|$(IMAGE_BASE))" || echo "No matching images found" image-clean: - @echo "๐Ÿงน Removing all $(IMAGE_BASE) images..." - @$(CONTAINER_RUNTIME) images --format "{{.Repository}}:{{.Tag}}" | \ - grep -E "(localhost/)?$(IMAGE_BASE)" | \ - xargs $(XARGS_FLAGS) $(CONTAINER_RUNTIME) rmi -f 2>/dev/null - @echo "โœ… Images cleaned" + @echo "๐Ÿงน Removing all $(IMAGE_BASE) images..." + @$(CONTAINER_RUNTIME) images --format "{{.Repository}}:{{.Tag}}" | \ + grep -E "(localhost/)?$(IMAGE_BASE)" | \ + xargs $(XARGS_FLAGS) $(CONTAINER_RUNTIME) rmi -f 2>/dev/null + @echo "โœ… Images cleaned" # Fix image naming issues image-retag: - @echo "๐Ÿท๏ธ Retagging images for consistency..." - @if [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ - if $(CONTAINER_RUNTIME) image exists $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null; then \ - $(CONTAINER_RUNTIME) tag $(IMAGE_BASE):$(IMAGE_TAG) $(IMAGE_LOCAL) 2>/dev/null || true; \ - fi; \ - else \ - if $(CONTAINER_RUNTIME) images -q $(IMAGE_LOCAL) 2>/dev/null | grep -q .; then \ - $(CONTAINER_RUNTIME) tag $(IMAGE_LOCAL) $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null || true; \ - fi; \ - fi - @echo "โœ… Images retagged" # This always shows success + @echo "๐Ÿท๏ธ Retagging images for consistency..." + @if [ "$(CONTAINER_RUNTIME)" = "podman" ]; then \ + if $(CONTAINER_RUNTIME) image exists $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null; then \ + $(CONTAINER_RUNTIME) tag $(IMAGE_BASE):$(IMAGE_TAG) $(IMAGE_LOCAL) 2>/dev/null || true; \ + fi; \ + else \ + if $(CONTAINER_RUNTIME) images -q $(IMAGE_LOCAL) 2>/dev/null | grep -q .; then \ + $(CONTAINER_RUNTIME) tag $(IMAGE_LOCAL) $(IMAGE_BASE):$(IMAGE_TAG) 2>/dev/null || true; \ + fi; \ + fi + @echo "โœ… Images retagged" # This always shows success # Runtime switching helpers use-docker: - @echo "export CONTAINER_RUNTIME=docker" - @echo "๐Ÿ’ก Run: export CONTAINER_RUNTIME=docker" + @echo "export CONTAINER_RUNTIME=docker" + @echo "๐Ÿ’ก Run: export CONTAINER_RUNTIME=docker" use-podman: - @echo "export CONTAINER_RUNTIME=podman" - @echo "๐Ÿ’ก Run: export CONTAINER_RUNTIME=podman" + @echo "export CONTAINER_RUNTIME=podman" + @echo "๐Ÿ’ก Run: export CONTAINER_RUNTIME=podman" show-runtime: - @echo "Current runtime: $(CONTAINER_RUNTIME)" - @echo "Detected from: $$(command -v $(CONTAINER_RUNTIME) || echo 'not found')" # Added - @echo "To switch: make use-docker or make use-podman" + @echo "Current runtime: $(CONTAINER_RUNTIME)" + @echo "Detected from: $$(command -v $(CONTAINER_RUNTIME) || echo 'not found')" # Added + @echo "To switch: make use-docker or make use-podman" {% endraw %} @@ -297,147 +297,153 @@ show-runtime: .PHONY: venv venv: - @rm -Rf "$(VENV_DIR)" - @test -d "$(VENVS_DIR)" || mkdir -p "$(VENVS_DIR)" - @python3 -m venv "$(VENV_DIR)" - @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m pip install --upgrade pip setuptools pdm uv" - @echo -e "โœ… Virtual env created.\n๐Ÿ’ก Enter it with:\n . $(VENV_DIR)/bin/activate\n" + @rm -Rf "$(VENV_DIR)" + @test -d "$(VENVS_DIR)" || mkdir -p "$(VENVS_DIR)" + @python3 -m venv "$(VENV_DIR)" + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m pip install --upgrade pip setuptools pdm uv" + @echo -e "โœ… Virtual env created.\n๐Ÿ’ก Enter it with:\n . $(VENV_DIR)/bin/activate\n" .PHONY: install install: venv - $(foreach bin,$(REQUIRED_BUILD_BINS), $(if $(shell command -v $(bin) 2> /dev/null),,$(error Couldn't find `$(bin)`))) - @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install ." + $(foreach bin,$(REQUIRED_BUILD_BINS), $(if $(shell command -v $(bin) 2> /dev/null),,$(error Couldn't find `$(bin)`))) + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install ." + +.PHONY: install-dev +install-dev: venv + $(foreach bin,$(REQUIRED_BUILD_BINS), $(if $(shell command -v $(bin) 2> /dev/null),,$(error Couldn't find `$(bin)`))) + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install -e .[dev]" .PHONY: install-editable install-editable: venv - $(foreach bin,$(REQUIRED_BUILD_BINS), $(if $(shell command -v $(bin) 2> /dev/null),,$(error Couldn't find `$(bin)`))) - @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install -e ." + $(foreach bin,$(REQUIRED_BUILD_BINS), $(if $(shell command -v $(bin) 2> /dev/null),,$(error Couldn't find `$(bin)`))) + @/bin/bash -c "source $(VENV_DIR)/bin/activate && python3 -m uv pip install -e .[dev]" .PHONY: uninstall uninstall: - pip uninstall $(PACKAGE_NAME) + pip uninstall $(PACKAGE_NAME) .PHONY: dist dist: clean ## Build wheel + sdist into ./dist - @test -d "$(VENV_DIR)" || $(MAKE) --no-print-directory venv - @/bin/bash -eu -c "\ - source $(VENV_DIR)/bin/activate && \ - python3 -m pip install --quiet --upgrade pip build && \ - python3 -m build" - @echo '๐Ÿ›  Wheel & sdist written to ./dist' + @test -d "$(VENV_DIR)" || $(MAKE) --no-print-directory venv + @/bin/bash -eu -c "\ + source $(VENV_DIR)/bin/activate && \ + python3 -m pip install --quiet --upgrade pip build && \ + python3 -m build" + @echo '๐Ÿ›  Wheel & sdist written to ./dist' .PHONY: wheel wheel: ## Build wheel only - @test -d "$(VENV_DIR)" || $(MAKE) --no-print-directory venv - @/bin/bash -eu -c "\ - source $(VENV_DIR)/bin/activate && \ - python3 -m pip install --quiet --upgrade pip build && \ - python3 -m build -w" - @echo '๐Ÿ›  Wheel written to ./dist' + @test -d "$(VENV_DIR)" || $(MAKE) --no-print-directory venv + @/bin/bash -eu -c "\ + source $(VENV_DIR)/bin/activate && \ + python3 -m pip install --quiet --upgrade pip build && \ + python3 -m build -w" + @echo '๐Ÿ›  Wheel written to ./dist' .PHONY: sdist sdist: ## Build source distribution only - @test -d "$(VENV_DIR)" || $(MAKE) --no-print-directory venv - @/bin/bash -eu -c "\ - source $(VENV_DIR)/bin/activate && \ - python3 -m pip install --quiet --upgrade pip build && \ - python3 -m build -s" - @echo '๐Ÿ›  Source distribution written to ./dist' + @test -d "$(VENV_DIR)" || $(MAKE) --no-print-directory venv + @/bin/bash -eu -c "\ + source $(VENV_DIR)/bin/activate && \ + python3 -m pip install --quiet --upgrade pip build && \ + python3 -m build -s" + @echo '๐Ÿ›  Source distribution written to ./dist' .PHONY: verify verify: dist ## Build, run metadata & manifest checks - @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ - twine check dist/* && \ - check-manifest && \ - pyroma -d ." - @echo "โœ… Package verified - ready to publish." + @/bin/bash -c "source $(VENV_DIR)/bin/activate && \ + twine check dist/* && \ + check-manifest && \ + pyroma -d ." + @echo "โœ… Package verified - ready to publish." .PHONY: lint-fix lint-fix: - @# Handle file arguments - @target_file="$(word 2,$(MAKECMDGOALS))"; \ - if [ -n "$$target_file" ] && [ "$$target_file" != "" ]; then \ - actual_target="$$target_file"; \ - else \ - actual_target="$(TARGET)"; \ - fi; \ - for target in $$(echo $$actual_target); do \ - if [ ! -e "$$target" ]; then \ - echo "โŒ File/directory not found: $$target"; \ - exit 1; \ - fi; \ - done; \ - echo "๐Ÿ”ง Fixing lint issues in $$actual_target..."; \ - $(MAKE) --no-print-directory black TARGET="$$actual_target"; \ - $(MAKE) --no-print-directory ruff-fix TARGET="$$actual_target" + @# Handle file arguments + @target_file="$(word 2,$(MAKECMDGOALS))"; \ + if [ -n "$$target_file" ] && [ "$$target_file" != "" ]; then \ + actual_target="$$target_file"; \ + else \ + actual_target="$(TARGET)"; \ + fi; \ + for target in $$(echo $$actual_target); do \ + if [ ! -e "$$target" ]; then \ + echo "โŒ File/directory not found: $$target"; \ + exit 1; \ + fi; \ + done; \ + echo "๐Ÿ”ง Fixing lint issues in $$actual_target..."; \ + $(MAKE) --no-print-directory black TARGET="$$actual_target"; \ + $(MAKE) --no-print-directory ruff-fix TARGET="$$actual_target" .PHONY: lint-check lint-check: - @# Handle file arguments - @target_file="$(word 2,$(MAKECMDGOALS))"; \ - if [ -n "$$target_file" ] && [ "$$target_file" != "" ]; then \ - actual_target="$$target_file"; \ - else \ - actual_target="$(TARGET)"; \ - fi; \ - for target in $$(echo $$actual_target); do \ - if [ ! -e "$$target" ]; then \ - echo "โŒ File/directory not found: $$target"; \ - exit 1; \ - fi; \ - done; \ - echo "๐Ÿ”ง Fixing lint issues in $$actual_target..."; \ - $(MAKE) --no-print-directory black-check TARGET="$$actual_target"; \ - $(MAKE) --no-print-directory ruff-check TARGET="$$actual_target" + @# Handle file arguments + @target_file="$(word 2,$(MAKECMDGOALS))"; \ + if [ -n "$$target_file" ] && [ "$$target_file" != "" ]; then \ + actual_target="$$target_file"; \ + else \ + actual_target="$(TARGET)"; \ + fi; \ + for target in $$(echo $$actual_target); do \ + if [ ! -e "$$target" ]; then \ + echo "โŒ File/directory not found: $$target"; \ + exit 1; \ + fi; \ + done; \ + echo "๐Ÿ”ง Fixing lint issues in $$actual_target..."; \ + $(MAKE) --no-print-directory black-check TARGET="$$actual_target"; \ + $(MAKE) --no-print-directory ruff-check TARGET="$$actual_target" .PHONY: lock lock: - $(foreach bin,$(REQUIRED_BUILD_BINS), $(if $(shell command -v $(bin) 2> /dev/null),,$(error Couldn't find `$(bin)`. Please run `make init`))) - uv lock + $(foreach bin,$(REQUIRED_BUILD_BINS), $(if $(shell command -v $(bin) 2> /dev/null),,$(error Couldn't find `$(bin)`. Please run `make init`))) + uv lock .PHONY: test test: - pytest tests + pytest tests .PHONY: serve serve: - @echo "Implement me." + @echo "Implement me." .PHONY: build build: - @$(MAKE) container-build + @$(MAKE) container-build .PHONY: start start: - @$(MAKE) container-run + @$(MAKE) container-run .PHONY: stop stop: - @$(MAKE) container-stop + @$(MAKE) container-stop .PHONY: clean clean: - find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete - rm -rf *.egg-info .pytest_cache tests/.pytest_cache build dist .ruff_cache .coverage + find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete + rm -rf *.egg-info .pytest_cache tests/.pytest_cache build dist .ruff_cache .coverage .PHONY: help help: - @echo "This Makefile is offered for convenience." - @echo "" - @echo "The following are the valid targets for this Makefile:" - @echo "...install Install package from sources" - @echo "...install-editable Install package from sources in editabled mode" - @echo "...uninstall Uninstall package" - @echo "...dist Clean-build wheel *and* sdist into ./dist" - @echo "...wheel Build wheel only" - @echo "...sdist Build source distribution only" - @echo "...verify Build + twine + check-manifest + pyroma (no upload)" - @echo "...serve Start API server locally" - @echo "...build Build API server container image" - @echo "...start Start the API server container" - @echo "...start Stop the API server container" - @echo "...lock Lock dependencies" - @echo "...lint-fix Check and fix lint errors" - @echo "...lint-check Check for lint errors" - @echo "...test Run all tests" - @echo "...clean Remove all artifacts and builds" + @echo "This Makefile is offered for convenience." + @echo "" + @echo "The following are the valid targets for this Makefile:" + @echo "...install Install package from sources" + @echo "...install-dev Install package from sources with dev packages" + @echo "...install-editable Install package from sources in editabled mode" + @echo "...uninstall Uninstall package" + @echo "...dist Clean-build wheel *and* sdist into ./dist" + @echo "...wheel Build wheel only" + @echo "...sdist Build source distribution only" + @echo "...verify Build + twine + check-manifest + pyroma (no upload)" + @echo "...serve Start API server locally" + @echo "...build Build API server container image" + @echo "...start Start the API server container" + @echo "...start Stop the API server container" + @echo "...lock Lock dependencies" + @echo "...lint-fix Check and fix lint errors" + @echo "...lint-check Check for lint errors" + @echo "...test Run all tests" + @echo "...clean Remove all artifacts and builds" diff --git a/plugin_templates/external/README.md.jinja b/plugin_templates/external/README.md.jinja index f841457e8..d9fb05cc7 100644 --- a/plugin_templates/external/README.md.jinja +++ b/plugin_templates/external/README.md.jinja @@ -5,8 +5,16 @@ ## Installation +To install dependencies with dev packages (required for linting and testing): + +```bash +make install-dev +``` + +Alternatively, you can also install it in editable mode: + ```bash -make install +make install-editable ``` ## Setting up the development environment diff --git a/plugin_templates/external/pyproject.toml.jinja b/plugin_templates/external/pyproject.toml.jinja index b6d493ed8..6eb6fa286 100644 --- a/plugin_templates/external/pyproject.toml.jinja +++ b/plugin_templates/external/pyproject.toml.jinja @@ -57,7 +57,28 @@ Repository = "https://github.com/IBM/mcp-context-forge" Changelog = "https://github.com/IBM/mcp-context-forge/blob/main/CHANGELOG.md" [tool.uv.sources] -mcp-contextforge-gateway = { git = "https://github.com/araujof/mcp-context-forge.git", rev = "feat/720-plugins-cli" } +mcp-contextforge-gateway = { git = "https://github.com/IBM/mcp-context-forge.git", rev = "main" } + +# ---------------------------------------------------------------- +# Optional dependency groups (extras) +# ---------------------------------------------------------------- +[project.optional-dependencies] +dev = [ + "black>=25.1.0", + "pytest>=8.4.1", + "pytest-asyncio>=1.1.0", + "pytest-cov>=6.2.1", + "pytest-dotenv>=0.5.2", + "pytest-env>=1.1.5", + "pytest-examples>=0.0.18", + "pytest-md-report>=0.7.0", + "pytest-rerunfailures>=15.1", + "pytest-trio>=0.8.0", + "pytest-xdist>=3.8.0", + "ruff>=0.12.9", + "unimport>=1.2.1", + "uv>=0.8.11", +] # -------------------------------------------------------------------- # ๐Ÿ”ง setuptools-specific configuration