Ejemplos de Integracion - mcp-scan¶
Esta guia proporciona ejemplos practicos y completos de como integrar mcp-scan en diferentes escenarios.
Indice¶
- Uso Basico
- Configuracion Personalizada
- Uso con Linea Base
- Modo Deep
- Procesamiento de Hallazgos
- Generacion de Reportes SARIF
- Verificacion de Cumplimiento MSSS
- Analisis de Superficie MCP
- Manejo de Errores
- Escaneo Concurrente
Uso Basico¶
El ejemplo mas simple de uso de mcp-scan:
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
// Crear configuracion por defecto
cfg := scanner.DefaultConfig()
// Crear instancia del scanner
s := scanner.New(cfg)
// Ejecutar escaneo
result, err := s.Scan(context.Background(), "./mi-servidor-mcp")
if err != nil {
log.Fatalf("Error en escaneo: %v", err)
}
// Mostrar resumen
fmt.Printf("Archivos escaneados: %d\n", result.Manifest.TotalFiles)
fmt.Printf("Hallazgos totales: %d\n", len(result.Findings))
fmt.Printf("Duracion: %v\n", result.ScanDuration)
// Mostrar hallazgos por severidad
for sev, count := range result.Summary.BySeverity {
fmt.Printf(" %s: %d\n", sev, count)
}
}
Usando la Funcion de Conveniencia¶
Para escaneos simples, usa la funcion Scan directamente:
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
result, err := scanner.Scan(
context.Background(),
"./mi-servidor-mcp",
scanner.DefaultConfig(),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Encontrados %d hallazgos\n", len(result.Findings))
}
Configuracion Personalizada¶
Ejemplo de configuracion avanzada:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mcphub/mcp-scan/pkg/scanner"
"github.com/mcphub/mcp-scan/internal/types"
)
func main() {
cfg := scanner.Config{
// Solo escanear archivos Python
Include: []string{"**/*.py"},
// Excluir tests y virtualenvs
Exclude: []string{
"**/test/**",
"**/tests/**",
"**/*_test.py",
"**/venv/**",
"**/.venv/**",
"**/site-packages/**",
},
// Modo rapido para CI/CD
Mode: scanner.ModeFast,
// Timeout de 2 minutos
Timeout: 2 * time.Minute,
// Fallar si hay hallazgos high o critical
FailOn: types.SeverityHigh,
// Solo ejecutar reglas de clase A (RCE) y E (secrets)
Rules: []string{"MCP-A*", "MCP-E*"},
// Usar 4 workers
Workers: 4,
// No redactar snippets (queremos ver el codigo)
RedactSnippets: false,
}
s := scanner.New(cfg)
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
// El ExitCode sera 1 si hay hallazgos high o critical
if result.ExitCode != 0 {
fmt.Println("FALLO: Se encontraron hallazgos de alta severidad")
for _, f := range result.Findings {
if f.Severity == types.SeverityHigh || f.Severity == types.SeverityCritical {
fmt.Printf(" - [%s] %s en %s\n",
f.Severity, f.RuleID, f.Location.String())
}
}
}
}
Uso con Linea Base¶
Gestionar hallazgos aceptados con lineas base:
Crear Linea Base desde Escaneo¶
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
s := scanner.New(scanner.DefaultConfig())
// Primer escaneo
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Hallazgos encontrados: %d\n", len(result.Findings))
// Crear linea base de todos los hallazgos actuales
baseline := s.GenerateBaseline(
result,
"Hallazgos revisados en auditoria inicial Q1-2026",
"security-team@example.com",
)
// Guardar linea base
if err := scanner.SaveBaseline(baseline, ".mcp-scan-baseline.json"); err != nil {
log.Fatal(err)
}
fmt.Println("Linea base guardada en .mcp-scan-baseline.json")
}
Escanear con Linea Base¶
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
// Configuracion con linea base
cfg := scanner.DefaultConfig()
cfg.Baseline = ".mcp-scan-baseline.json"
s := scanner.New(cfg)
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Nuevos hallazgos: %d\n", len(result.Findings))
fmt.Printf("Hallazgos filtrados por baseline: %d\n", result.BaselinedCount)
// Solo procesar hallazgos nuevos
if len(result.Findings) > 0 {
fmt.Println("\nNuevos hallazgos detectados:")
for _, f := range result.Findings {
fmt.Printf(" [%s] %s - %s:%d\n",
f.Severity, f.RuleID, f.Location.File, f.Location.StartLine)
}
}
}
Aplicar Linea Base Manualmente¶
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
s := scanner.New(scanner.DefaultConfig())
// Escanear sin linea base
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total hallazgos: %d\n", len(result.Findings))
// Cargar y aplicar linea base manualmente
baseline, err := scanner.LoadBaseline(".mcp-scan-baseline.json")
if err != nil {
log.Fatal(err)
}
filteredCount := s.ApplyBaseline(result, baseline)
fmt.Printf("Filtrados: %d\n", filteredCount)
fmt.Printf("Hallazgos restantes: %d\n", len(result.Findings))
}
Combinar Multiples Lineas Base¶
package main
import (
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
// Cargar lineas base existentes
baseline1, err := scanner.LoadBaseline("baseline-team-a.json")
if err != nil {
log.Fatal(err)
}
baseline2, err := scanner.LoadBaseline("baseline-team-b.json")
if err != nil {
log.Fatal(err)
}
// Combinar en una sola
added := baseline1.Merge(baseline2)
fmt.Printf("Entradas agregadas de baseline-team-b: %d\n", added)
// Guardar linea base combinada
if err := scanner.SaveBaseline(baseline1, "baseline-combined.json"); err != nil {
log.Fatal(err)
}
// Ver estadisticas
stats := baseline1.GetStats()
fmt.Printf("Total entradas: %d\n", stats.TotalEntries)
fmt.Println("Por regla:")
for ruleID, count := range stats.ByRuleID {
fmt.Printf(" %s: %d\n", ruleID, count)
}
}
Modo Deep¶
Usar analisis inter-procedural para mayor precision:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
cfg := scanner.Config{
Include: []string{"**/*.py", "**/*.ts"},
Exclude: []string{"**/node_modules/**", "**/venv/**"},
// Modo deep para analisis inter-procedural
Mode: scanner.ModeDeep,
// Timeout mayor para analisis profundo
Timeout: 15 * time.Minute,
// Mas workers para procesar call graph
Workers: 8,
}
s := scanner.New(cfg)
ctx := context.Background()
result, err := s.Scan(ctx, "./mi-proyecto")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Modo: %s\n", result.Mode)
fmt.Printf("Duracion: %v\n", result.ScanDuration)
fmt.Printf("Hallazgos: %d\n", len(result.Findings))
// Los hallazgos en modo deep pueden tener trazas mas largas
for _, f := range result.Findings {
if f.Trace != nil && len(f.Trace.Steps) > 2 {
fmt.Printf("\nHallazgo con traza inter-procedural:\n")
fmt.Printf(" Regla: %s\n", f.RuleID)
fmt.Printf(" Source: %s\n", f.Trace.Source.String())
fmt.Printf(" Sink: %s\n", f.Trace.Sink.String())
fmt.Printf(" Pasos: %d\n", len(f.Trace.Steps))
for i, step := range f.Trace.Steps {
fmt.Printf(" %d. [%s] %s en %s\n",
i+1, step.Action, step.Variable, step.Location.String())
}
}
}
}
Procesamiento de Hallazgos¶
Ejemplos de como procesar y filtrar hallazgos:
Filtrar por Severidad¶
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
"github.com/mcphub/mcp-scan/internal/types"
)
func filterBySeverity(findings []types.Finding, minSeverity types.Severity) []types.Finding {
var filtered []types.Finding
minLevel := minSeverity.Level()
for _, f := range findings {
if f.Severity.Level() >= minLevel {
filtered = append(filtered, f)
}
}
return filtered
}
func main() {
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
// Solo hallazgos high y critical
severos := filterBySeverity(result.Findings, types.SeverityHigh)
fmt.Printf("Total: %d, Severos: %d\n",
len(result.Findings), len(severos))
for _, f := range severos {
fmt.Printf("[%s] %s: %s\n", f.Severity, f.RuleID, f.Title)
}
}
Agrupar por Clase de Vulnerabilidad¶
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
"github.com/mcphub/mcp-scan/internal/types"
)
func groupByClass(findings []types.Finding) map[types.VulnClass][]types.Finding {
groups := make(map[types.VulnClass][]types.Finding)
for _, f := range findings {
groups[f.Class] = append(groups[f.Class], f)
}
return groups
}
func main() {
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
groups := groupByClass(result.Findings)
classNames := map[types.VulnClass]string{
types.ClassA: "RCE (Remote Code Execution)",
types.ClassB: "Filesystem Traversal",
types.ClassC: "SSRF/Network",
types.ClassD: "SQL Injection",
types.ClassE: "Secrets/Tokens",
types.ClassF: "Auth/OAuth",
types.ClassG: "Tool Poisoning",
}
for class, findings := range groups {
name := classNames[class]
if name == "" {
name = string(class)
}
fmt.Printf("\n%s (%d hallazgos):\n", name, len(findings))
for _, f := range findings {
fmt.Printf(" - %s: %s (%s:%d)\n",
f.RuleID, f.Title, f.Location.File, f.Location.StartLine)
}
}
}
Buscar Hallazgos en Contexto MCP¶
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
"github.com/mcphub/mcp-scan/internal/types"
)
func findingsInMCPTools(findings []types.Finding) []types.Finding {
var inTools []types.Finding
for _, f := range findings {
if f.MCPContext != nil && f.MCPContext.ToolName != "" {
inTools = append(inTools, f)
}
}
return inTools
}
func main() {
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
toolFindings := findingsInMCPTools(result.Findings)
fmt.Printf("Hallazgos en tools MCP: %d de %d\n",
len(toolFindings), len(result.Findings))
for _, f := range toolFindings {
fmt.Printf("\n[%s] %s\n", f.Severity, f.RuleID)
fmt.Printf(" Tool: %s\n", f.MCPContext.ToolName)
fmt.Printf(" Handler: %s\n", f.MCPContext.HandlerName)
fmt.Printf(" Ubicacion: %s\n", f.Location.String())
if f.Evidence.Snippet != "" {
fmt.Printf(" Codigo:\n %s\n", f.Evidence.Snippet)
}
}
}
Generacion de Reportes SARIF¶
Generar reportes SARIF para integracion con GitHub/GitLab:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
// Generar reporte SARIF
sarifReport, err := s.GenerateReport(result, "sarif")
if err != nil {
log.Fatal(err)
}
// Guardar archivo SARIF
if err := os.WriteFile("scan-results.sarif", sarifReport, 0644); err != nil {
log.Fatal(err)
}
fmt.Println("Reporte SARIF guardado en scan-results.sarif")
// Tambien generar JSON para analisis interno
jsonReport, err := s.GenerateReport(result, "json")
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("scan-results.json", jsonReport, 0644); err != nil {
log.Fatal(err)
}
fmt.Println("Reporte JSON guardado en scan-results.json")
// Generar bundle de evidencias para auditoria
evidenceReport, err := s.GenerateReport(result, "evidence")
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("scan-evidence.json", evidenceReport, 0644); err != nil {
log.Fatal(err)
}
fmt.Println("Bundle de evidencias guardado en scan-evidence.json")
}
Subir SARIF a GitHub¶
package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func uploadSARIFToGitHub(sarifReport []byte, repo, sha, ref string) error {
token := os.Getenv("GITHUB_TOKEN")
if token == "" {
return fmt.Errorf("GITHUB_TOKEN no configurado")
}
// Comprimir y codificar en base64
encoded := base64.StdEncoding.EncodeToString(sarifReport)
payload := map[string]interface{}{
"commit_sha": sha,
"ref": ref,
"sarif": encoded,
}
jsonPayload, err := json.Marshal(payload)
if err != nil {
return err
}
url := fmt.Sprintf("https://api.github.com/repos/%s/code-scanning/sarifs", repo)
req, err := http.NewRequest("POST", url, bytes.NewReader(jsonPayload))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK {
return fmt.Errorf("GitHub API error: %s", resp.Status)
}
return nil
}
func main() {
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
sarifReport, err := s.GenerateReport(result, "sarif")
if err != nil {
log.Fatal(err)
}
// Variables de CI/CD
repo := os.Getenv("GITHUB_REPOSITORY") // "owner/repo"
sha := os.Getenv("GITHUB_SHA") // commit sha
ref := os.Getenv("GITHUB_REF") // "refs/heads/main"
if err := uploadSARIFToGitHub(sarifReport, repo, sha, ref); err != nil {
log.Fatalf("Error subiendo SARIF: %v", err)
}
fmt.Println("SARIF subido a GitHub Code Scanning")
}
Verificacion de Cumplimiento MSSS¶
Verificar si un servidor cumple con los estandares de seguridad:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
score := result.MSSSScore
fmt.Println("=== Reporte de Cumplimiento MSSS ===")
fmt.Printf("Version MSSS: %s\n", score.Version)
fmt.Printf("Puntuacion Total: %.1f/100\n", score.Total)
fmt.Printf("Nivel: %d\n", score.Level)
levelNames := []string{
"No Cumple",
"Basico",
"Enterprise",
"Certificado",
}
fmt.Printf("Estado: %s\n", levelNames[score.Level])
fmt.Println("\n--- Puntuacion por Categoria ---")
for class, cat := range score.Categories {
if cat.Findings > 0 {
fmt.Printf(" Clase %s: %.1f/%.1f (-%d hallazgos)\n",
class, cat.Score, cat.MaxScore, cat.Findings)
} else {
fmt.Printf(" Clase %s: %.1f/%.1f (limpio)\n",
class, cat.Score, cat.MaxScore)
}
}
// Detalles de calculo
if score.Details != nil {
fmt.Printf("\n--- Detalles ---\n")
fmt.Printf(" Puntuacion base: %.1f\n", score.Details.BaseScore)
fmt.Printf(" Total penalizaciones: %.1f\n", score.Details.Penalties)
}
// Verificar requisitos por nivel
fmt.Println("\n--- Requisitos ---")
fmt.Printf(" Nivel 1 (Basico): Score >= 60, <= 3 high %s\n",
checkMark(score.Total >= 60))
fmt.Printf(" Nivel 2 (Enterprise): Score >= 80, 0 high %s\n",
checkMark(score.Total >= 80 && score.Level >= 2))
fmt.Printf(" Nivel 3 (Certified): Score >= 90, 0 high %s\n",
checkMark(score.Total >= 90 && score.Level >= 3))
// Salir con codigo apropiado
if !score.Compliant {
fmt.Println("\nFALLO: No cumple con el nivel minimo de seguridad")
os.Exit(1)
}
fmt.Printf("\nOK: Cumple con nivel %d (%s)\n",
score.Level, levelNames[score.Level])
}
func checkMark(ok bool) string {
if ok {
return "[OK]"
}
return "[X]"
}
Verificar Nivel Minimo Requerido¶
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
// Nivel minimo requerido (configurable)
requiredLevel := 2 // Enterprise
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
actualLevel := result.MSSSScore.Level
if actualLevel < requiredLevel {
fmt.Printf("FALLO: Nivel actual %d, requerido %d\n",
actualLevel, requiredLevel)
// Mostrar que falta para alcanzar el nivel
if result.MSSSScore.Total < 80 {
fmt.Printf(" - Necesita %.1f puntos mas para llegar a 80\n",
80-result.MSSSScore.Total)
}
// Contar hallazgos high
highCount := result.Summary.BySeverity["high"]
if highCount > 0 {
fmt.Printf(" - Debe resolver %d hallazgos de severidad high\n",
highCount)
}
os.Exit(1)
}
fmt.Printf("OK: Nivel %d cumple requisito minimo de %d\n",
actualLevel, requiredLevel)
}
Analisis de Superficie MCP¶
Examinar la superficie de ataque MCP de un servidor:
package main
import (
"context"
"fmt"
"log"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
s := scanner.New(scanner.DefaultConfig())
result, err := s.Scan(context.Background(), "./src")
if err != nil {
log.Fatal(err)
}
surf := result.MCPSurface
fmt.Println("=== Superficie MCP Detectada ===")
// Transporte
fmt.Printf("\nTransporte: %s\n", surf.Transport)
// Tools
fmt.Printf("\nTools MCP (%d):\n", len(surf.Tools))
for i, tool := range surf.Tools {
fmt.Printf(" %d. %s\n", i+1, tool.Name)
if tool.Description != "" {
fmt.Printf(" Descripcion: %s\n", tool.Description)
}
if tool.Handler != nil {
fmt.Printf(" Handler: %s en %s\n",
tool.Handler.FunctionName, tool.Handler.FileName)
}
fmt.Printf(" Ubicacion: %s\n", tool.Location.String())
}
// Resources
if len(surf.Resources) > 0 {
fmt.Printf("\nResources MCP (%d):\n", len(surf.Resources))
for i, res := range surf.Resources {
fmt.Printf(" %d. %s\n", i+1, res.Name)
if res.Handler != nil {
fmt.Printf(" Handler: %s\n", res.Handler.FunctionName)
}
}
}
// Prompts
if len(surf.Prompts) > 0 {
fmt.Printf("\nPrompts MCP (%d):\n", len(surf.Prompts))
for i, prompt := range surf.Prompts {
fmt.Printf(" %d. %s\n", i+1, prompt.Name)
}
}
// Auth signals
if len(surf.AuthSignals) > 0 {
fmt.Printf("\nSenales de Autenticacion (%d):\n", len(surf.AuthSignals))
for _, signal := range surf.AuthSignals {
fmt.Printf(" - %s: %s en %s\n",
signal.Type, signal.Name, signal.Location.String())
}
}
// Resumen de superficie de ataque
fmt.Println("\n=== Resumen de Superficie de Ataque ===")
fmt.Printf(" Total entry points: %d\n",
len(surf.Tools)+len(surf.Resources)+len(surf.Prompts))
fmt.Printf(" Tools (alto riesgo): %d\n", len(surf.Tools))
fmt.Printf(" Resources: %d\n", len(surf.Resources))
fmt.Printf(" Prompts: %d\n", len(surf.Prompts))
if surf.Transport == "http" || surf.Transport == "websocket" {
fmt.Println(" ALERTA: Transporte de red detectado - verificar autenticacion")
}
}
Manejo de Errores¶
Ejemplos de manejo robusto de errores:
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func scanWithErrorHandling(path string) (*scanner.Result, error) {
cfg := scanner.Config{
Include: []string{"**/*.py", "**/*.ts", "**/*.js"},
Exclude: []string{"**/node_modules/**"},
Mode: scanner.ModeFast,
Timeout: 5 * time.Minute,
}
s := scanner.New(cfg)
// Crear contexto con timeout
ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout)
defer cancel()
result, err := s.Scan(ctx, path)
if err != nil {
// Verificar tipo de error
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("timeout: el escaneo excedio %v", cfg.Timeout)
}
if errors.Is(err, context.Canceled) {
return nil, fmt.Errorf("cancelado: el escaneo fue cancelado")
}
// Otros errores
return nil, fmt.Errorf("error en escaneo: %w", err)
}
return result, nil
}
func main() {
path := "./src"
// Verificar que el path existe
if _, err := os.Stat(path); os.IsNotExist(err) {
log.Fatalf("Error: la ruta '%s' no existe", path)
}
result, err := scanWithErrorHandling(path)
if err != nil {
log.Fatal(err)
}
// Verificar si hay archivos
if result.Manifest.TotalFiles == 0 {
fmt.Println("Advertencia: no se encontraron archivos para escanear")
return
}
fmt.Printf("Escaneo completado: %d archivos, %d hallazgos\n",
result.Manifest.TotalFiles, len(result.Findings))
}
Cancelacion Manual¶
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func main() {
s := scanner.New(scanner.DefaultConfig())
// Crear contexto cancelable
ctx, cancel := context.WithCancel(context.Background())
// Capturar Ctrl+C
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
fmt.Println("\nRecibida senal de cancelacion...")
cancel()
}()
// Ejecutar escaneo
result, err := s.Scan(ctx, "./src")
if err != nil {
if ctx.Err() != nil {
fmt.Println("Escaneo cancelado por el usuario")
os.Exit(130) // Codigo estandar para Ctrl+C
}
log.Fatal(err)
}
fmt.Printf("Hallazgos: %d\n", len(result.Findings))
}
Escaneo Concurrente¶
Escanear multiples directorios en paralelo:
package main
import (
"context"
"fmt"
"log"
"sync"
"time"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
type ScanJob struct {
Path string
Name string
}
type ScanResult struct {
Job ScanJob
Result *scanner.Result
Error error
}
func scanMultiple(jobs []ScanJob) []ScanResult {
cfg := scanner.DefaultConfig()
cfg.Timeout = 3 * time.Minute
var wg sync.WaitGroup
results := make(chan ScanResult, len(jobs))
// Crear un scanner por goroutine
for _, job := range jobs {
wg.Add(1)
go func(j ScanJob) {
defer wg.Done()
s := scanner.New(cfg)
result, err := s.Scan(context.Background(), j.Path)
results <- ScanResult{
Job: j,
Result: result,
Error: err,
}
}(job)
}
// Esperar a que terminen
go func() {
wg.Wait()
close(results)
}()
// Recolectar resultados
var allResults []ScanResult
for r := range results {
allResults = append(allResults, r)
}
return allResults
}
func main() {
jobs := []ScanJob{
{Path: "./service-a", Name: "Service A"},
{Path: "./service-b", Name: "Service B"},
{Path: "./service-c", Name: "Service C"},
}
fmt.Printf("Escaneando %d proyectos en paralelo...\n", len(jobs))
results := scanMultiple(jobs)
totalFindings := 0
for _, r := range results {
if r.Error != nil {
fmt.Printf("\n[%s] ERROR: %v\n", r.Job.Name, r.Error)
continue
}
findings := len(r.Result.Findings)
totalFindings += findings
fmt.Printf("\n[%s]\n", r.Job.Name)
fmt.Printf(" Archivos: %d\n", r.Result.Manifest.TotalFiles)
fmt.Printf(" Hallazgos: %d\n", findings)
fmt.Printf(" MSSS Score: %.1f (Nivel %d)\n",
r.Result.MSSSScore.Total, r.Result.MSSSScore.Level)
}
fmt.Printf("\n=== Total: %d hallazgos en %d proyectos ===\n",
totalFindings, len(jobs))
}
Limitar Concurrencia¶
package main
import (
"context"
"fmt"
"sync"
"github.com/mcphub/mcp-scan/pkg/scanner"
)
func scanWithSemaphore(paths []string, maxConcurrent int) map[string]*scanner.Result {
cfg := scanner.DefaultConfig()
results := make(map[string]*scanner.Result)
var mu sync.Mutex
sem := make(chan struct{}, maxConcurrent)
var wg sync.WaitGroup
for _, path := range paths {
wg.Add(1)
go func(p string) {
defer wg.Done()
// Adquirir slot
sem <- struct{}{}
defer func() { <-sem }()
s := scanner.New(cfg)
result, err := s.Scan(context.Background(), p)
if err != nil {
fmt.Printf("Error en %s: %v\n", p, err)
return
}
mu.Lock()
results[p] = result
mu.Unlock()
}(path)
}
wg.Wait()
return results
}
func main() {
paths := []string{
"./project-1",
"./project-2",
"./project-3",
"./project-4",
"./project-5",
}
// Maximo 3 escaneos simultaneos
results := scanWithSemaphore(paths, 3)
for path, result := range results {
fmt.Printf("%s: %d hallazgos\n", path, len(result.Findings))
}
}