@@ -64,6 +64,17 @@ os-deps: $(OS_DEPS_SCRIPT)
64
64
@bash $(OS_DEPS_SCRIPT )
65
65
66
66
67
+ # -----------------------------------------------------------------------------
68
+ # 🔧 HELPER SCRIPTS
69
+ # -----------------------------------------------------------------------------
70
+ # Helper to ensure a Python package is installed in venv
71
+ define ensure_pip_package
72
+ @test -d "$(VENV_DIR ) " || $(MAKE ) venv
73
+ @/bin/bash -c "source $(VENV_DIR ) /bin/activate && \
74
+ python3 -m pip show $(1 ) >/dev/null 2>&1 || \
75
+ python3 -m pip install -q $(1 ) "
76
+ endef
77
+
67
78
# =============================================================================
68
79
# 🌱 VIRTUAL ENVIRONMENT & INSTALLATION
69
80
# =============================================================================
@@ -291,9 +302,23 @@ pip-licenses:
291
302
@echo " 📜 License inventory written to $( LICENSES_MD) "
292
303
293
304
scc :
305
+ @command -v scc > /dev/null 2>&1 || { \
306
+ echo " ❌ scc not installed." ; \
307
+ echo " 💡 Install with:" ; \
308
+ echo " • macOS: brew install scc" ; \
309
+ echo " • Linux: Download from https://github.com/boyter/scc/releases" ; \
310
+ exit 1; \
311
+ }
294
312
@scc --by-file -i py,sh .
295
313
296
314
scc-report :
315
+ @command -v scc > /dev/null 2>&1 || { \
316
+ echo " ❌ scc not installed." ; \
317
+ echo " 💡 Install with:" ; \
318
+ echo " • macOS: brew install scc" ; \
319
+ echo " • Linux: Download from https://github.com/boyter/scc/releases" ; \
320
+ exit 1; \
321
+ }
297
322
@mkdir -p $(dir $(METRICS_MD ) )
298
323
@printf " # Lines of Code Report\n\n" > $(METRICS_MD )
299
324
@scc . --format=html-table >> $(METRICS_MD )
@@ -318,16 +343,12 @@ endif
318
343
.PHONY : docs
319
344
docs : images sbom
320
345
@echo " 📚 Generating documentation with handsdown..."
321
- uv handsdown --external https://github.com/yourorg/$(PROJECT_NAME ) / \
322
- -o $(DOCS_DIR ) /docs \
323
- -n app --name " $( PROJECT_NAME) " --cleanup
324
-
325
- @echo "🔧 Rewriting GitHub links..."
326
- @find $(DOCS_DIR)/docs/app -type f \
327
- -exec sed $(SED_INPLACE) 's# https://github.com/yourorg#https://github.com/ibm/mcp-context-forge#g' {} +
328
-
329
- @sed $(SED_INPLACE) 's# https://github.com/yourorg#https://github.com/ibm/mcp-context-forge#g' \
330
- $(DOCS_DIR)/docs/README.md
346
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
347
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
348
+ python3 -m pip install -q handsdown && \
349
+ python3 -m handsdown --external https://github.com/IBM/mcp-context-forge/ \
350
+ -o $(DOCS_DIR ) /docs \
351
+ -n app --name ' $(PROJECT_NAME)' --cleanup"
331
352
332
353
@cp README.md $(DOCS_DIR)/docs/index.md
333
354
@echo "✅ Docs ready in $(DOCS_DIR)/docs"
@@ -336,14 +357,26 @@ docs: images sbom
336
357
images :
337
358
@echo " 🖼️ Generating documentation diagrams..."
338
359
@mkdir -p $(DOCS_DIR ) /docs/design/images
339
- @code2flow mcpgateway/ --output $(DOCS_DIR ) /docs/design/images/code2flow.dot || true
340
- @dot -Tsvg -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=14 -Efontcolor=black $(DOCS_DIR ) /docs/design/images/code2flow.dot -o $(DOCS_DIR ) /docs/design/images/code2flow.svg || true
341
- @/bin/bash -c " source $( VENV_DIR) /bin/activate && python3 -m pip install snakefood3"
342
- @/bin/bash -c " source $( VENV_DIR) /bin/activate && python3 -m snakefood3 . mcpgateway > snakefood.dot"
343
- @dot -Tpng -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=12 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=10 -Efontcolor=black snakefood.dot -o $(DOCS_DIR ) /docs/design/images/snakefood.png || true
344
- @pyreverse --colorized mcpgateway || true
345
- @dot -Tsvg -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=14 -Efontcolor=black packages.dot -o $(DOCS_DIR ) /docs/design/images/packages.svg || true
346
- @dot -Tsvg -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=14 -Efontcolor=black classes.dot -o $(DOCS_DIR ) /docs/design/images/classes.svg || true
360
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
361
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
362
+ python3 -m pip install -q code2flow && \
363
+ $(VENV_DIR ) /bin/code2flow mcpgateway/ --output $(DOCS_DIR ) /docs/design/images/code2flow.dot || true"
364
+ @command -v dot > /dev/null 2>&1 || { \
365
+ echo " ⚠️ Graphviz (dot) not installed - skipping diagram generation" ; \
366
+ echo " 💡 Install with: brew install graphviz (macOS) or apt-get install graphviz (Linux)" ; \
367
+ } && \
368
+ dot -Tsvg -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=14 -Efontcolor=black $(DOCS_DIR ) /docs/design/images/code2flow.dot -o $(DOCS_DIR ) /docs/design/images/code2flow.svg || true
369
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
370
+ python3 -m pip install -q snakefood3 && \
371
+ python3 -m snakefood3 . mcpgateway > snakefood.dot"
372
+ @command -v dot > /dev/null 2>&1 && \
373
+ dot -Tpng -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=12 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=10 -Efontcolor=black snakefood.dot -o $(DOCS_DIR ) /docs/design/images/snakefood.png || true
374
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
375
+ python3 -m pip install -q pylint && \
376
+ $(VENV_DIR ) /bin/pyreverse --colorized mcpgateway || true"
377
+ @command -v dot > /dev/null 2>&1 && \
378
+ dot -Tsvg -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=14 -Efontcolor=black packages.dot -o $(DOCS_DIR ) /docs/design/images/packages.svg || true && \
379
+ dot -Tsvg -Gbgcolor=transparent -Gfontname=" Arial" -Nfontname=" Arial" -Nfontsize=14 -Nfontcolor=black -Nfillcolor=white -Nshape=box -Nstyle=" filled,rounded" -Ecolor=gray -Efontname=" Arial" -Efontsize=14 -Efontcolor=black classes.dot -o $(DOCS_DIR ) /docs/design/images/classes.svg || true
347
380
@rm -f packages.dot classes.dot snakefood.dot || true
348
381
349
382
# =============================================================================
@@ -473,9 +506,13 @@ fawltydeps: ## 🏗️ Dependency sanity
473
506
@$(VENV_DIR ) /bin/fawltydeps --detailed --exclude ' docs/**' . || true
474
507
475
508
wily : # # 📈 Maintainability report
509
+ @echo " 📈 Maintainability report..."
510
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
476
511
@git stash --quiet
477
- @wily build -n 10 . > /dev/null || true
478
- @wily report . || true
512
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
513
+ python3 -m pip install -q wily && \
514
+ python3 -m wily build -n 10 . > /dev/null || true && \
515
+ python3 -m wily report . || true"
479
516
@git stash pop --quiet
480
517
481
518
pyre : # # 🧠 Facebook Pyre analysis
@@ -485,15 +522,28 @@ pyrefly: ## 🧠 Facebook Pyrefly analysis (faster,
485
522
@$(VENV_DIR ) /bin/pyrefly check mcpgateway
486
523
487
524
depend : # # 📦 List dependencies
488
- pdm list --freeze
525
+ @echo " 📦 List dependencies"
526
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
527
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
528
+ python3 -m pip install -q pdm && \
529
+ python3 -m pdm list --freeze"
489
530
490
531
snakeviz : # # 🐍 Interactive profile visualiser
491
- @python3 -m cProfile -o mcp.prof app/server.py && snakeviz mcp.prof --server
532
+ @echo " 🐍 Interactive profile visualiser..."
533
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
534
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
535
+ python3 -m pip install -q snakeviz && \
536
+ python3 -m cProfile -o mcp.prof mcpgateway/main.py && \
537
+ python3 -m snakeviz mcp.prof --server"
492
538
493
539
pstats : # # 📊 Static call-graph image
494
- @python3 -m cProfile -o mcp.pstats app/server.py && \
495
- gprof2dot -w -e 3 -n 3 -s -f pstats mcp.pstats | \
496
- dot -Tpng -o $(DOCS_DIR ) /pstats.png
540
+ @echo " 📊 Static call-graph image"
541
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
542
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
543
+ python3 -m pip install -q gprof2dot && \
544
+ python3 -m cProfile -o mcp.pstats mcpgateway/main.py && \
545
+ $(VENV_DIR ) /bin/gprof2dot -w -e 3 -n 3 -s -f pstats mcp.pstats | \
546
+ dot -Tpng -o $(DOCS_DIR ) /pstats.png"
497
547
498
548
spellcheck-sort : .spellcheck-en.txt # # 🔤 Sort spell-list
499
549
sort -d -f -o $< $<
@@ -563,10 +613,24 @@ grype-install:
563
613
@curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
564
614
565
615
grype-scan :
616
+ @command -v grype > /dev/null 2>&1 || { \
617
+ echo " ❌ grype not installed." ; \
618
+ echo " 💡 Install with:" ; \
619
+ echo " • curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin" ; \
620
+ echo " • Or run: make grype-install" ; \
621
+ exit 1; \
622
+ }
566
623
@echo " 🔍 Grype vulnerability scan..."
567
624
@grype $(IMG ) :latest --scope all-layers --only-fixed
568
625
569
626
grype-sarif :
627
+ @command -v grype > /dev/null 2>&1 || { \
628
+ echo " ❌ grype not installed." ; \
629
+ echo " 💡 Install with:" ; \
630
+ echo " • curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin" ; \
631
+ echo " • Or run: make grype-install" ; \
632
+ exit 1; \
633
+ }
570
634
@echo " 📄 Generating Grype SARIF report..."
571
635
@grype $(IMG ) :latest --scope all-layers --output sarif --file grype-results.sarif
572
636
@@ -587,22 +651,31 @@ LINTERS += yamllint jsonlint tomllint
587
651
.PHONY : yamllint jsonlint tomllint
588
652
589
653
yamllint : # # 📑 YAML linting
590
- @command -v yamllint > /dev/null 2>&1 || { \
591
- echo ' ❌ yamllint not installed ➜ pip install yamllint' ; exit 1; }
592
- @echo ' 📑 yamllint ...' && $(VENV_DIR ) /bin/yamllint -c .yamllint .
654
+ @echo ' 📑 yamllint ...'
655
+ $(call ensure_pip_package,yamllint)
656
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
657
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
658
+ python3 -m pip install -q yamllint 2> /dev/null || true"
659
+ @$(VENV_DIR ) /bin/yamllint -c .yamllint .
593
660
594
661
jsonlint : # # 📑 JSON validation (jq)
595
662
@command -v jq > /dev/null 2>&1 || { \
596
- echo ' ❌ jq not installed ➜ sudo apt-get install jq' ; exit 1; }
663
+ echo " ❌ jq not installed." ; \
664
+ echo " 💡 Install with:" ; \
665
+ echo " • macOS: brew install jq" ; \
666
+ echo " • Linux: sudo apt-get install jq" ; \
667
+ exit 1; \
668
+ }
597
669
@echo ' 📑 jsonlint (jq) ...'
598
670
@find . -type f -name ' *.json' -not -path ' ./node_modules/*' -print0 \
599
671
| xargs -0 -I{} sh -c ' jq empty "{}"' \
600
672
&& echo ' ✅ All JSON valid'
601
673
602
674
tomllint : # # 📑 TOML validation (tomlcheck)
603
- @command -v tomlcheck > /dev/null 2>&1 || { \
604
- echo ' ❌ tomlcheck not installed ➜ pip install tomlcheck' ; exit 1; }
605
675
@echo ' 📑 tomllint (tomlcheck) ...'
676
+ @test -d " $( VENV_DIR) " || $(MAKE ) venv
677
+ @/bin/bash -c " source $( VENV_DIR) /bin/activate && \
678
+ python3 -m pip install -q tomlcheck 2> /dev/null || true"
606
679
@find . -type f -name ' *.toml' -print0 \
607
680
| xargs -0 -I{} $(VENV_DIR ) /bin/tomlcheck " {}"
608
681
@@ -666,11 +739,25 @@ osv-install: ## Install/upgrade osv-scanner
666
739
667
740
# ─────────────── Source directory scan ────────────────────────────────────────
668
741
osv-scan-source :
742
+ @command -v osv-scanner > /dev/null 2>&1 || { \
743
+ echo " ❌ osv-scanner not installed." ; \
744
+ echo " 💡 Install with:" ; \
745
+ echo " • go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest" ; \
746
+ echo " • Or run: make osv-install" ; \
747
+ exit 1; \
748
+ }
669
749
@echo " 🔍 osv-scanner source scan..."
670
750
@osv-scanner scan source --recursive .
671
751
672
752
# ─────────────── Container image scan ─────────────────────────────────────────
673
753
osv-scan-image :
754
+ @command -v osv-scanner > /dev/null 2>&1 || { \
755
+ echo " ❌ osv-scanner not installed." ; \
756
+ echo " 💡 Install with:" ; \
757
+ echo " • go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest" ; \
758
+ echo " • Or run: make osv-install" ; \
759
+ exit 1; \
760
+ }
674
761
@echo " 🔍 osv-scanner image scan..."
675
762
@CONTAINER_CLI=$$(command -v docker || command -v podman ) ; \
676
763
if [ -n " $$ CONTAINER_CLI" ]; then \
@@ -804,7 +891,15 @@ trivy-install:
804
891
@curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
805
892
806
893
trivy :
807
- @systemctl --user enable --now podman.socket
894
+ @command -v trivy > /dev/null 2>&1 || { \
895
+ echo " ❌ trivy not installed." ; \
896
+ echo " 💡 Install with:" ; \
897
+ echo " • macOS: brew install trivy" ; \
898
+ echo " • Linux: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin" ; \
899
+ echo " • Or run: make trivy-install" ; \
900
+ exit 1; \
901
+ }
902
+ @systemctl --user enable --now podman.socket 2> /dev/null || true
808
903
@echo " 🔎 trivy vulnerability scan..."
809
904
@trivy --format table --severity HIGH,CRITICAL image $(IMG )
810
905
@@ -813,8 +908,13 @@ trivy:
813
908
DOCKLE_IMAGE ?= $(IMG ) :latest # mcpgateway/mcpgateway:latest from your build
814
909
dockle:
815
910
@echo "🔎 dockle scan (tar mode) on $(DOCKLE_IMAGE)..."
816
- @command -v dockle >/dev/null || { \
817
- echo '❌ Dockle not installed. See https://github.com/goodwithtech/dockle'; exit 1; }
911
+ @command -v dockle >/dev/null 2>&1 || { \
912
+ echo "❌ dockle not installed."; \
913
+ echo "💡 Install with:"; \
914
+ echo " • macOS: brew install goodwithtech/r/dockle"; \
915
+ echo " • Linux: Download from https://github.com/goodwithtech/dockle/releases"; \
916
+ exit 1; \
917
+ }
818
918
819
919
# Pick docker or podman-whichever is on PATH
820
920
@CONTAINER_CLI=$$(command -v docker || command -v podman) ; \
@@ -2191,11 +2291,17 @@ shell-linters-install: ## 🔧 Install shellcheck, shfmt, bashate
2191
2291
2192
2292
shell-lint : shell-linters-install # # 🔍 Run shfmt, ShellCheck & bashate
2193
2293
@echo " 🔍 Running shfmt (diff-only)..."
2194
- @shfmt -d -i 4 -ci $(SHELL_SCRIPTS ) || true
2294
+ @command -v shfmt > /dev/null 2>&1 || { \
2295
+ echo " ⚠️ shfmt not installed - skipping" ; \
2296
+ echo " 💡 Install with: go install mvdan.cc/sh/v3/cmd/shfmt@latest" ; \
2297
+ } && shfmt -d -i 4 -ci $(SHELL_SCRIPTS ) || true
2195
2298
@echo " 🔍 Running ShellCheck..."
2196
- @shellcheck $(SHELL_SCRIPTS ) || true
2299
+ @command -v shellcheck > /dev/null 2>&1 || { \
2300
+ echo " ⚠️ shellcheck not installed - skipping" ; \
2301
+ echo " 💡 Install with: brew install shellcheck (macOS) or apt-get install shellcheck (Linux)" ; \
2302
+ } && shellcheck $(SHELL_SCRIPTS ) || true
2197
2303
@echo " 🔍 Running bashate..."
2198
- @$(VENV_DIR ) /bin/bashate -C $(SHELL_SCRIPTS ) || true
2304
+ @$(VENV_DIR ) /bin/bashate $(SHELL_SCRIPTS ) || true
2199
2305
@echo " ✅ Shell lint complete."
2200
2306
2201
2307
0 commit comments