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¶
3.4 Prompt¶
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:
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:
Con nombre heuristico:
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:
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¶
- Decoradores dinamicos: No detecta
@getattr(server, "tool")() - Alias de imports:
from mcp import Server as Spuede fallar - Descripcion en docstring: Solo extrae de argumentos del decorador
- Tools sin decorador: Solo por heuristica de nombre
- 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