Skip to content

Extraccion de Superficie MCP

Documento tecnico para analistas de seguridad


1. Introduccion

La extraccion de superficie MCP identifica todos los puntos de exposicion de un servidor MCP: herramientas (tools), recursos (resources), prompts, tipo de transporte, y senales de autenticacion. Esta informacion es fundamental para contextualizar el analisis de seguridad.


2. Arquitectura del Extractor

2.1 Componentes

+------------------+
|   AST Files      |  <-- Lista de archivos parseados
+------------------+
        |
        v
+------------------+
|    Extractor     |  <-- internal/surface/surface.go
+------------------+
        |
        +---> detectSDK()
        +---> extractToolFromFunction()
        +---> detectTransport()
        +---> detectAuthSignals()
        |
        v
+------------------+
|   MCPSurface     |
| - Tools          |
| - Resources      |
| - Prompts        |
| - Transport      |
| - AuthSignals    |
+------------------+

2.2 Ubicacion del Codigo

Archivo principal: internal/surface/surface.go

type MCPSurface struct {
    Tools       []Tool
    Resources   []Resource
    Prompts     []Prompt
    Transport   TransportType
    AuthSignals []AuthSignal
}

3. Estructura de Datos

3.1 Tool

type Tool struct {
    Name        string                 // Nombre de la herramienta
    Description string                 // Descripcion (puede contener injection)
    Schema      map[string]interface{} // JSON Schema de parametros
    Handler     *HandlerRef            // Referencia a funcion handler
    Decorators  []string               // Decoradores aplicados
    Location    types.Location         // Posicion en codigo
}

3.2 HandlerRef

type HandlerRef struct {
    FunctionName string         // Nombre de la funcion
    FileName     string         // Archivo donde esta definida
    Location     types.Location // Posicion exacta
}

3.3 Resource

type Resource struct {
    Name        string
    Description string
    Handler     *HandlerRef
    Location    types.Location
}

3.4 Prompt

type Prompt struct {
    Name        string
    Description string
    Handler     *HandlerRef
    Location    types.Location
}

3.5 TransportType

type TransportType string

const (
    TransportSTDIO     TransportType = "stdio"
    TransportHTTP      TransportType = "http"
    TransportWebSocket TransportType = "websocket"
    TransportUnknown   TransportType = "unknown"
)

3.6 AuthSignal

type AuthSignal struct {
    Type     string         // "cookie", "header", "oauth_state"
    Name     string         // Nombre de la funcion/variable
    Location types.Location
}

4. Deteccion de SDK

4.1 SDKs Soportados

SDK Lenguaje Import Pattern
Official Python Python mcp, mcp.*
FastMCP Python fastmcp, fastmcp.*
Official TypeScript TS/JS @modelcontextprotocol/sdk

4.2 Algoritmo de Deteccion

type sdkType int

const (
    sdkUnknown sdkType = iota
    sdkPythonOfficial
    sdkPythonFastMCP
    sdkTypeScriptOfficial
)

func (e *Extractor) detectSDK(file *ast.File) sdkType {
    for _, imp := range file.Imports {
        // Python official SDK
        if imp.Module == "mcp" || strings.HasPrefix(imp.Module, "mcp.") {
            return sdkPythonOfficial
        }
        // FastMCP
        if imp.Module == "fastmcp" || strings.HasPrefix(imp.Module, "fastmcp.") {
            return sdkPythonFastMCP
        }
        // TypeScript official SDK
        if imp.Module == "@modelcontextprotocol/sdk" {
            return sdkTypeScriptOfficial
        }
    }
    return sdkUnknown
}

4.3 Ejemplos de Imports

Python Official:

from mcp import Server
from mcp.types import Tool
import mcp

FastMCP:

from fastmcp import FastMCP
import fastmcp

TypeScript Official:

import { Server } from '@modelcontextprotocol/sdk';
import { Tool } from '@modelcontextprotocol/sdk/types';


5. Extraccion de Tools

5.1 Metodo de Extraccion

func (e *Extractor) extractToolFromFunction(fn ast.Function, filePath string, sdk sdkType) *Tool {
    // 1. Buscar decoradores de tool
    for _, dec := range fn.Decorators {
        if e.isToolDecorator(dec.Name, sdk) {
            tool := &Tool{
                Name:       fn.Name,
                Decorators: []string{dec.Name},
                Handler: &HandlerRef{
                    FunctionName: fn.Name,
                    FileName:     filePath,
                    Location:     fn.Location,
                },
                Location: fn.Location,
            }

            // Extraer descripcion de argumentos del decorador
            if len(dec.Arguments) > 0 {
                if str, ok := dec.Arguments[0].(*ast.StringLiteral); ok {
                    tool.Description = str.Value
                }
            }

            return tool
        }
    }

    // 2. Heuristica: funciones con nombres de tool
    if e.looksLikeTool(fn.Name) {
        return &Tool{
            Name: fn.Name,
            Handler: &HandlerRef{
                FunctionName: fn.Name,
                FileName:     filePath,
                Location:     fn.Location,
            },
            Location: fn.Location,
        }
    }

    return nil
}

5.2 Decoradores de Tool Reconocidos

func (e *Extractor) isToolDecorator(name string, sdk sdkType) bool {
    toolDecorators := []string{
        "tool",
        "server.tool",
        "mcp.tool",
        "app.tool",
        "fastmcp.tool",
    }

    for _, td := range toolDecorators {
        if name == td {
            return true
        }
    }

    return false
}

5.3 Patrones de Nombres de Tool

Heuristica para detectar tools sin decorador explicito:

func (e *Extractor) looksLikeTool(name string) bool {
    toolPrefixes := []string{
        "handle_",
        "tool_",
        "execute_",
        "run_",
        "do_",
    }

    toolSuffixes := []string{
        "_tool",
        "_handler",
        "_action",
        "_command",
    }

    nameLower := strings.ToLower(name)

    for _, prefix := range toolPrefixes {
        if strings.HasPrefix(nameLower, prefix) {
            return true
        }
    }

    for _, suffix := range toolSuffixes {
        if strings.HasSuffix(nameLower, suffix) {
            return true
        }
    }

    return false
}

5.4 Ejemplos de Tools Detectados

Con decorador:

@server.tool()
def calculate(a: int, b: int):
    """Calculate sum of two numbers"""
    return a + b

Con nombre heuristico:

def handle_file_read(path: str):
    """Read file contents"""
    return open(path).read()


6. Deteccion de Transporte

6.1 Algoritmo

func (e *Extractor) detectTransport(file *ast.File) TransportType {
    // 1. Revisar imports
    for _, imp := range file.Imports {
        moduleLower := strings.ToLower(imp.Module)

        if strings.Contains(moduleLower, "stdio") {
            return TransportSTDIO
        }
        if strings.Contains(moduleLower, "http") ||
           strings.Contains(moduleLower, "express") ||
           strings.Contains(moduleLower, "fastapi") ||
           strings.Contains(moduleLower, "flask") {
            return TransportHTTP
        }
        if strings.Contains(moduleLower, "websocket") ||
           strings.Contains(moduleLower, "ws") {
            return TransportWebSocket
        }
    }

    // 2. Revisar llamadas a funciones
    for _, fn := range file.Functions {
        for _, stmt := range fn.Body {
            if exprStmt, ok := stmt.(*ast.ExpressionStatement); ok {
                if call, ok := exprStmt.Expression.(*ast.Call); ok {
                    fnName := strings.ToLower(call.Function)
                    if strings.Contains(fnName, "stdio") {
                        return TransportSTDIO
                    }
                    if strings.Contains(fnName, "listen") ||
                       strings.Contains(fnName, "serve") {
                        return TransportHTTP
                    }
                }
            }
        }
    }

    return TransportUnknown
}

6.2 Patrones por Transporte

STDIO:

# Imports
from mcp.server.stdio import stdio_server
import sys

# Llamadas
server.run_stdio()
server.serve_stdio()

HTTP:

# Imports
from fastapi import FastAPI
from flask import Flask
import express

# Llamadas
app.listen(8080)
uvicorn.run(app, port=8080)

WebSocket:

# Imports
import websockets
from websocket import WebSocket
import ws

# Llamadas
ws.connect()


7. Deteccion de Senales de Autenticacion

7.1 Algoritmo

func (e *Extractor) detectAuthSignals(file *ast.File) []AuthSignal {
    var signals []AuthSignal

    // Combinar funciones de nivel superior y metodos de clase
    allFunctions := file.Functions
    for _, class := range file.Classes {
        allFunctions = append(allFunctions, class.Methods...)
    }

    for _, fn := range allFunctions {
        for _, stmt := range fn.Body {
            if exprStmt, ok := stmt.(*ast.ExpressionStatement); ok {
                if call, ok := exprStmt.Expression.(*ast.Call); ok {
                    fnName := strings.ToLower(call.Function)

                    // Cookie detection
                    if strings.Contains(fnName, "cookie") ||
                       strings.Contains(fnName, "set_cookie") {
                        signals = append(signals, AuthSignal{
                            Type:     "cookie",
                            Name:     call.Function,
                            Location: call.Location,
                        })
                    }

                    // Header detection
                    if strings.Contains(fnName, "header") ||
                       strings.Contains(fnName, "authorization") {
                        signals = append(signals, AuthSignal{
                            Type:     "header",
                            Name:     call.Function,
                            Location: call.Location,
                        })
                    }

                    // OAuth state detection
                    if strings.Contains(fnName, "state") ||
                       strings.Contains(fnName, "nonce") ||
                       strings.Contains(fnName, "oauth") {
                        signals = append(signals, AuthSignal{
                            Type:     "oauth_state",
                            Name:     call.Function,
                            Location: call.Location,
                        })
                    }
                }
            }
        }
    }

    return signals
}

7.2 Tipos de Senales

Tipo Descripcion Funciones Detectadas
cookie Manejo de cookies set_cookie, get_cookie, cookie
header Headers de autenticacion set_header, authorization, header
oauth_state OAuth con state state, nonce, oauth

8. Procesamiento de Archivos

8.1 Flujo Principal

func (e *Extractor) Extract(files []*ast.File) (*MCPSurface, error) {
    surface := &MCPSurface{
        Transport: TransportUnknown,
    }

    for _, file := range files {
        e.extractFromFile(file, surface)
    }

    return surface, nil
}

func (e *Extractor) extractFromFile(file *ast.File, surface *MCPSurface) {
    // 1. Detectar SDK
    sdkType := e.detectSDK(file)

    // 2. Extraer tools de funciones
    for _, fn := range file.Functions {
        if tool := e.extractToolFromFunction(fn, file.Path, sdkType); tool != nil {
            surface.Tools = append(surface.Tools, *tool)
        }
    }

    // 3. Extraer tools de metodos de clase
    for _, class := range file.Classes {
        for _, method := range class.Methods {
            if tool := e.extractToolFromFunction(method, file.Path, sdkType); tool != nil {
                surface.Tools = append(surface.Tools, *tool)
            }
        }
    }

    // 4. Detectar transporte
    if t := e.detectTransport(file); t != TransportUnknown {
        surface.Transport = t
    }

    // 5. Detectar senales de auth
    surface.AuthSignals = append(surface.AuthSignals, e.detectAuthSignals(file)...)
}

9. Uso de la Superficie en Analisis

9.1 En Taint Analysis

La superficie determina que parametros se marcan como tainted:

func (e *Engine) isToolHandler(fn ast.Function, surf *surface.MCPSurface) bool {
    // 1. Verificar decoradores
    for _, dec := range fn.Decorators {
        if strings.Contains(strings.ToLower(dec.Name), "tool") {
            return true
        }
    }

    // 2. Verificar contra superficie
    if surf != nil {
        for _, tool := range surf.Tools {
            if tool.Handler != nil && tool.Handler.FunctionName == fn.Name {
                return true
            }
        }
    }

    return false
}

9.2 En Pattern Detection

La superficie proporciona contexto para detectores:

func (d *PromptInjectionDetector) Detect(file *ast.File, surf *surface.MCPSurface) []Match {
    var matches []Match

    // Analizar descripciones de tools
    if surf != nil {
        for _, tool := range surf.Tools {
            descLower := strings.ToLower(tool.Description)
            for _, marker := range injectionMarkers {
                if strings.Contains(descLower, marker) {
                    matches = append(matches, Match{
                        Location: tool.Location,
                        Snippet:  tool.Description,
                        Context:  "Tool: " + tool.Name,
                    })
                }
            }
        }
    }

    return matches
}

9.3 En ML Classifier

Clasifica descripciones de tools:

if surf != nil {
    for _, tool := range surf.Tools {
        result := classifier.Classify(tool.Description)
        if result.IsInjection {
            // Reportar hallazgo
        }
    }
}

10. Output de la Superficie

10.1 JSON Output

{
  "surface": {
    "tools": [
      {
        "name": "read_file",
        "description": "Read a file from disk",
        "handler": {
          "function_name": "read_file",
          "file_name": "server.py",
          "location": {
            "file": "server.py",
            "start_line": 10,
            "end_line": 15
          }
        },
        "decorators": ["server.tool"],
        "location": {
          "file": "server.py",
          "start_line": 9,
          "end_line": 15
        }
      }
    ],
    "resources": [],
    "prompts": [],
    "transport": "stdio",
    "auth_signals": [
      {
        "type": "header",
        "name": "set_authorization_header",
        "location": {
          "file": "server.py",
          "start_line": 25
        }
      }
    ]
  }
}

10.2 Uso en CLI

$ mcp-scan surface ./server.py

MCP Surface Analysis
====================

SDK Detected: Python Official (mcp)
Transport: STDIO

Tools (3):
  1. read_file
     Description: "Read a file from disk"
     Handler: server.py:10

  2. write_file
     Description: "Write content to a file"
     Handler: server.py:20

  3. execute_command
     Description: "Execute a shell command"
     Handler: server.py:30

Resources: None

Prompts: None

Auth Signals:
  - header: set_authorization_header (server.py:25)

11. Limitaciones

11.1 Limitaciones de Deteccion

  1. Decoradores dinamicos: No detecta @getattr(server, "tool")()
  2. Alias de imports: from mcp import Server as S puede fallar
  3. Descripcion en docstring: Solo extrae de argumentos del decorador
  4. Tools sin decorador: Solo por heuristica de nombre
  5. TypeScript: Soporte parcial para clases

11.2 Falsos Positivos de Heuristica

Funciones con nombres como tool_utils o handle_error pueden ser detectadas incorrectamente.

11.3 Falsos Negativos

  • Tools registrados programaticamente
  • Decoradores personalizados
  • Metaprogramacion

12. Ejemplo Completo

Input

# server.py
from mcp import Server
from mcp.server.stdio import stdio_server

server = Server("example-server")

@server.tool()
def read_file(path: str):
    """Read contents of a file from disk."""
    with open(path) as f:
        return f.read()

@server.tool()
def calculate(a: int, b: int):
    """Calculate the sum of two numbers."""
    return a + b

def helper_function():
    """Not a tool"""
    pass

def handle_request(req):
    """Heuristic match - might be tool"""
    pass

if __name__ == "__main__":
    stdio_server(server)

Superficie Extraida

{
  "tools": [
    {
      "name": "read_file",
      "description": "Read contents of a file from disk.",
      "decorators": ["server.tool"],
      "handler": {
        "function_name": "read_file",
        "file_name": "server.py",
        "location": {"start_line": 7}
      }
    },
    {
      "name": "calculate",
      "description": "Calculate the sum of two numbers.",
      "decorators": ["server.tool"],
      "handler": {
        "function_name": "calculate",
        "file_name": "server.py",
        "location": {"start_line": 13}
      }
    },
    {
      "name": "handle_request",
      "description": "",
      "decorators": [],
      "handler": {
        "function_name": "handle_request",
        "file_name": "server.py",
        "location": {"start_line": 22}
      }
    }
  ],
  "resources": [],
  "prompts": [],
  "transport": "stdio",
  "auth_signals": []
}

Notas: - read_file y calculate detectados por decorador - handle_request detectado por heuristica (prefix handle_) - helper_function NO detectado (no cumple criterios) - Transporte stdio detectado por import y llamada a stdio_server


Siguiente documento: limitaciones.md