Skip to content

Ejemplos de Integracion - mcp-scan

Esta guia proporciona ejemplos practicos y completos de como integrar mcp-scan en diferentes escenarios.


Indice


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))
    }
}