Una introducción a los servidores de protocolo de contexto del modelo remoto

Escribí por última vez sobre el Modelo de Contexto del Protocolo (MCP) en diciembre de 2024, poco antes del crecimiento exponencial del tema en lo que es hoy. Recuerdo haber pensado en ese momento que una de las cosas clave que sentí que tenía que sucederle a MCP para que la tecnología cambie el juego era la capacidad de los clientes de MCP para acceder a los servidores MCP no locales. Eso ya está sucediendo, por supuesto, pero ¿qué pasa si quieres un pedazo de esta acción? ¿Cómo se hace para escribir un servidor MCP remoto, elaborando herramientas útiles para él, probándolo?luego implementarlo en la nube, por ejemplo, para que cualquiera pueda acceder a las herramientas que expone a cualquier cliente compatible en cualquier parte del mundo.

Te mostraré cómo hacer todas esas cosas en este artículo.

Un resumen rápido de lo que es un servidor MCP

Hay docenas de definiciones para lo que es un servidor MCP. En mi opinión, que probablemente sea una especie de simplificación excesiva, un servidor MCP permite que los clientes habilitados para MCP, como Cursor y Claude Code, llamen a funciones útiles que contiene el servidor MCP.
¿En qué se diferencia de usted simplemente escribiendo un montón de herramientas valiosas y llamándolas en su código?

Bueno, la clave es que estás escribiendo esas herramientas. ¿Qué pasa con el universo potencial de las herramientas que existen que alguien más ha escrito? Estoy seguro de que has escuchado la expresión “… hay una aplicación para eso”. En el futuro no demasiado lejano, eso podría convertirse “… hay un servidor MCP para eso“Ok, no tan ágil sino tan innovador.

Hasta ahora, la gran mayoría de los servidores MCP se han escrito con el tipo de transporte Stdio en mente. Esto significa que la responsabilidad está en para alojar el servidor en su sistema local. Eso a veces puede ser complicado y propenso al error. Además, solo usted puede acceder a ese servidor. Y ahí es donde los servidores MCP remotos (o HTTP transmitibles) entran en los suyos. Alojado de forma remota, solo necesita conocer la URL del servidor y los nombres de las herramientas que proporciona, y está en funcionamiento con ella en segundos.

Entonces, si ha escrito algo que otros pueden encontrar realmente útil, ¿por qué no hacer un servidor MCP remoto, alojarlo en la nube y dejar que otros lo usen también?

Ok, hagamos esto.

Mi configuración

Desarrollaré el código para el servidor MCP y sus herramientas utilizando el código de Windows y Microsoft Visual Studio. Usaré Git Bash para mi línea de comando, ya que viene con algunas utilidades útiles que usaré, como rizo y sed. También necesitarás instalar Nodo.js y el uva Utilidad de paquetes de Python. Si desea implementar el servidor MCP terminado en la nube, también necesitará almacenar su código en GitHub, por lo que necesitará una cuenta para eso.

Lo primero que debe hacer es inicializar un nuevo proyecto para su código, etc. Use el uva herramienta con la bandera init para esto. A continuación, agregamos un entorno, cambiamos a él y agregamos todas las bibliotecas externas que usará nuestro código.

$ uv init remote-mcp
Initialized project `remote-mcp` at `/home/tom/projects/remote-mcp`
$ cd remote-mcp
$ ls -al
total 28
drwxr-xr-x 3 tom tom 4096 Jun 23 17:42 .
drwxr-xr-x 14 tom tom 4096 Jun 23 17:42 ..
drwxr-xr-x 7 tom tom 4096 Jun 23 17:42 .git
-rw-r--r-- 1 tom tom 109 Jun 23 17:42 .gitignore
-rw-r--r-- 1 tom tom 5 Jun 23 17:42 .python-version
-rw-r--r-- 1 tom tom 0 Jun 23 17:42 README.md
-rw-r--r-- 1 tom tom 88 Jun 23 17:42 main.py
-rw-r--r-- 1 tom tom 156 Jun 23 17:42 pyproject.toml

$ uv venv && source .venv/bin/activate
# Now, install the libraries we will use.
(remote-mcp) $ uv add fastapi 'uvicorn[standard]' mcp-server requests yfinance python-dotenv

Lo que desarrollaremos

Desarrollaremos un servidor MCP y dos herramientas distintas para que nuestro servidor MCP utilice. El primero será un controlador de premios Nobel. Usted proporciona un año, por ejemplo, 1935, y un tema, por ejemplo, física, y el servidor MCP devolverá información sobre quién ganó el premio ese año en ese tema. La segunda herramienta devolverá la temperatura máxima registrada para una ciudad la semana pasada.

En primer lugar, codificaremos nuestras dos herramientas y las probaremos localmente. A continuación, incorporaremos las herramientas en un servidor MCP que se ejecuta localmente y probaremos esa configuración. Si funciona como se esperaba, podemos implementar el servidor MCP y sus herramientas en un servidor de nube remoto y verificar que continúe funcionando correctamente.

Código Ejemplo 1: obtener información del Premio Nobel

Los servicios del sitio web del Premio Nobel tienen licencia bajo la Licencia Creative Commons Zero. Puede ver los detalles utilizando el siguiente enlace:

https://www.nobelprize.org/about/terms-of-use-for-api-nobelprize-org-and-data-nobelprize-org

Aquí está la función base que usaremos. Abra su editor de código y guarde este contenido en un archivo llamado premio_tool.py.

import requests
import os
import io
import csv

# from mcp.server.fastmcp import FastMCP
try:
    from mcp.server.fastmcp import FastMCP
except ModuleNotFoundError:
    # Try importing from a local path if running locally
    import sys
    sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
    from fastmcp import FastMCP

mcp = FastMCP(name="nobelChecker",stateless_http=True)

@mcp.tool()
def nobel_checker(year, subject):
    """
    Finds the Nobel Prize winner(s) for a given year and subject using the Nobel Prize API.

    Args:
        year (int): The year of the prize.
        subject (str): The category of the prize (e.g., 'physics', 'chemistry', 'peace').

    Returns:
        list: A list of strings, where each string is the full name of a winner.
              Returns an empty list if no prize was awarded or if an error occurred.
    """
    BASE_URL = "http://api.nobelprize.org/v1/prize.csv"
    
    # Prepare the parameters for the request, converting subject to lowercase
    # to match the API's expectation.
    params = {
        'year': year,
        'category': subject.lower()
    }
    
    try:
        # Make the request using the safe 'params' argument
        response = requests.get(BASE_URL, params=params)
        
        # This will raise an exception for bad status codes (like 404 or 500)
        response.raise_for_status()

        # If the API returns no data (e.g., no prize that year), the text will
        # often just be the header row. We check if there's more than one line.
        if len(response.text.splitlines()) <= 1:
            return [] # No winners found

        # Use io.StringIO to treat the response text (a string) like a file
        csv_file = io.StringIO(response.text)
        
        # Use DictReader to easily access columns by name
        reader = csv.DictReader(csv_file)
        
        winners = []
        for row in reader:
            full_name = f"{row['firstname']} {row['surname']}"
            winners.append(full_name)
            
        return winners

    except requests.exceptions.RequestException as e:
        print(f"An error occurred during the API request: {e}")
        return [] # Return an empty list on network or HTTP errors

if __name__ == "__main__":
   data =  nobel_checker(1921,"Physics")
   print(data)

Este script define una pequeña herramienta MCP (protocolo de contexto del modelo) que se puede ejecutar localmente o dentro de un servidor FastMCP. Después de intentar importar FastMCP desde mcp.server paquete y volver a caer a un hermano fastmcp módulo si esa importación falla. Luego construye una instancia de MCP llamada chaquetero Con el indicador Stiteless_http = True, lo que significa que FastMCP expondrá automáticamente un punto final HTTP simple para las llamadas de una sola vez. La función decorada nobel_checker se convierte en una herramienta MCP. Cuando invocarEd, construye una consulta a la API REST utilizando el año suministrado y el tema, y ​​devuelve los nombres del ganador del premio para ese año y sujeto (o un mensaje útil si no).

Si ejecutamos el código anterior localmente, obtenemos una salida similar a la siguiente, lo que indica que la función funciona correctamente y realiza su tarea prevista.

['Albert Einstein']

Código Ejemplo 2: obtener información de temperatura de la ciudad

Para nuestra segunda función base, escribiremos una herramienta que devuelva la temperatura más alta para una ciudad durante la última semana. Los datos meteorológicos son proporcionados por Open-Meteo.com. En su página de licencia (“https://open-meteo.com/en/license), dice,

“Los datos de la API se ofrecen bajo Atribución 4.0 Internacional (CC por 4.0)

Eres libre de compartir: Copie y redistribuya el material en cualquier medio o formato y adaptar: remix, transformar y construir sobre el material. “

He dado la atribución correcta y el enlace a su licencia, que cumple con los términos de su licencia.

Crea el archivo Python temp_tool.py e ingrese este código.

# temp_tool.py

from mcp.server.fastmcp import FastMCP

mcp = FastMCP(name="stockChecker", stateless_http=True)

import requests
from datetime import datetime, timedelta

# This helper function can be reused. It's not tied to a specific API provider.
def get_coords_for_city(city_name):
    """
    Converts a city name to latitude and longitude using a free, open geocoding service.
    """
    # Using Open-Meteo's geocoding, which is also free and requires no key.
    GEO_URL = "https://geocoding-api.open-meteo.com/v1/search"
    params = {'name': city_name, 'count': 1, 'language': 'en', 'format': 'json'}
    
    try:
        response = requests.get(GEO_URL, params=params)
        response.raise_for_status()
        data = response.json()
        
        if not data.get('results'):
            print(f"Error: City '{city_name}' not found.")
            return None, None
            
        # Extract the very first result
        location = data['results'][0]
        return location['latitude'], location['longitude']
        
    except requests.exceptions.RequestException as e:
        print(f"API request error during geocoding: {e}")
        return None, None

@mcp.tool()
def get_historical_weekly_high(city_name):
    """
    Gets the highest temperature for a city over the previous 7 days using the
    commercially-friendly Open-Meteo API.

    Args:
        city_name (str): The name of the city (e.g., "New York", "London").

    Returns:
        float: The highest temperature in Fahrenheit from the period, or None if an error occurs.
    """
    # 1. Get the coordinates for the city
    lat, lon = get_coords_for_city(city_name)
    if lat is None or lon is None:
        return None # Exit if city wasn't found
        
    # 2. Calculate the date range for the last week
    end_date = datetime.now() - timedelta(days=1)
    start_date = datetime.now() - timedelta(days=7)
    start_date_str = start_date.strftime('%Y-%m-%d')
    end_date_str = end_date.strftime('%Y-%m-%d')

    # 3. Prepare the API request for the Historical API
    HISTORICAL_URL = "https://archive-api.open-meteo.com/v1/era5"
    params = {
        'latitude': lat,
        'longitude': lon,
        'start_date': start_date_str,
        'end_date': end_date_str,
        'daily': 'temperature_2m_max', # The specific variable for daily max temp
        'temperature_unit': 'fahrenheit' # This API handles units correctly
    }
    
    try:
        print(f"Fetching historical weekly max temp for {city_name.title()}...")
        response = requests.get(HISTORICAL_URL, params=params)
        response.raise_for_status()
        data = response.json()
        
        daily_data = data.get('daily', {})
        max_temps = daily_data.get('temperature_2m_max', [])
        
        if not max_temps:
            print("Could not find historical temperature data in the response.")
            return None
            
        # 4. Find the single highest temperature from the list of daily highs
        highest_temp = max(max_temps)
        
        return round(highest_temp, 1)

    except requests.exceptions.RequestException as e:
        print(f"API request error during historical fetch: {e}")
        return None

if __name__ == "__main__":
   data =  get_historical_weekly_high("New York")
   print(data)

Esta función toma un nombre de la ciudad y devuelve la temperatura más alta registrada en la ciudad durante la última semana.

Aquí hay una salida típica cuando se ejecuta localmente.

Fetching historical weekly max temp for New York...
104.3

Creando nuestro servidor MCP

Ahora que hemos demostrado que nuestras funciones están funcionando, incorporemos en un servidor MCP y lo ejecutemos localmente. Aquí está el código del servidor que necesitará.

# mcp_server.py

import contextlib
from fastapi import FastAPI
from temp_tool import mcp as temp_mcp
from prize_tool import mcp as prize_mcp
import os
from dotenv import load_dotenv

load_dotenv()

# Create a combined lifespan to manage both session managers
@contextlib.asynccontextmanager
async def lifespan(app: FastAPI):
    async with contextlib.AsyncExitStack() as stack:
        await stack.enter_async_context(temp_mcp.session_manager.run())
        await stack.enter_async_context(prize_mcp.session_manager.run())
        yield


app = FastAPI(lifespan=lifespan)
app.mount("/temp", temp_mcp.streamable_http_app())
app.mount("/prize", prize_mcp.streamable_http_app())

PORT = int(os.getenv("PORT", "10000"))

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=PORT)

Los únicos cambios en nuestros códigos originales premios_tool y temp_tool son eliminar las tres líneas en la parte inferior de cada una, que se utilizan para las pruebas. Retire estos de ambos.

if __name__ == "__main__":
    data = nobel_checker(1921,"Physics")
    print(data)

and ...

if __name__ == "__main__":
    data = get_historical_weekly_high("New York")
    print(data)

Ejecutando el servidor MCP localmente

Para ejecutar nuestro servidor, escriba el siguiente comando en un terminal de línea de comandos.

$ uvicorn mcp_server:app --reload --port 10000
$ # You can also use python mcp_server.py --reload --port 10000
$ #
INFO: Will watch for changes in these directories: ['C:\\Users\\thoma\\projects\\remote-mcp\\remote-mcp']
INFO: Uvicorn running on http://127.0.0.1:10000 (Press CTRL+C to quit)
INFO: Started reloader process [3308] using WatchFiles
INFO: Started server process [38428]
INFO: Waiting for application startup.
[06/25/25 08:36:22] INFO StreamableHTTP session manager started streamable_http_manager.py:109
INFO StreamableHTTP session manager started streamable_http_manager.py:109
INFO: Application startup complete.

Prueba de nuestro servidor MCP localmente

Podemos usar un terminal de comando gitbash y curl para esto. Asegúrese de que su servidor esté en funcionamiento primero. Probemos primero nuestra herramienta de verificación de temperatura. La salida siempre se puede procesar para obtener exactamente el contenido que desea en un formato más fácil de usar.

$ curl -sN -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_historical_weekly_high","arguments":{"city_name":"New York"}}}' http://localhost:10000/temp/mcp/ | sed -n '/^data:/{s/^data: //;p}'


{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"104.3"}],"isError":false}}

Esto muestra que la temperatura máxima en Nueva York durante la última semana fue 104.3 Fahrenheit.

Y ahora podemos probar la herramienta de verificación de premios.

$ curl -sN -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"nobel_checker","arguments":{"year":1921,"category":"Physics"}}}' http://localhost:10000/prize/mcp/ | sed -n '/^data:/{s/^data: //;p}' 

{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Albert Einstein"}],"isError":false}}

Albert Einstein ganó el Premio Nobel de Física en 1921.

Implementar nuestro servidor MCP de forma remota

Ahora que estamos satisfechos con nuestro código y que el servidor MCP está funcionando como se esperaba localmente, la siguiente etapa es implementarlo de forma remota, permitiendo que cualquier persona en el mundo lo use. Hay algunas opciones para hacer esto, pero quizás la más fácil (e inicialmente la más barata) es utilizar un servicio como Prestar.

Render es una plataforma moderna de alojamiento en la nube, como una alternativa más simple a AWS, Heroku o Vercel, que le permite implementar aplicaciones, API, bases de datos, trabajadores de fondo y más de STRACK con una sobrecarga de devops mínima. Más concretamente es que es gratis comenzar y es más que suficiente para nuestras necesidades. Así que dirígete a su sitio web y regístrese.

Antes de implementar con Render, debe comprometer y enviar su código a un repositorio GitHub (o un GitLab/BitBucket). Después de eso, en el sitio web de Render, elija crear un nuevo servidor web,

Imagen del sitio web de Render

La primera vez, Render solicitará acceso a su cuenta GitHub (o Bitbucket/GitLab).

Imagen del sitio web de Render

Después de eso, debe proporcionar los comandos para construir su implementación e iniciar su servidor. Por ejemplo ….

Imagen del sitio web de Render

De vuelta en el Ajustes pantalla, haga clic en el Manual Desplegar -> Implementar la última confirmación Elemento de menú y un registro del proceso de compilación e implementación se mostrará. Después de unos minutos, debería ver los siguientes mensajes que indican que su implementación fue exitosa.

...
...
==> Build successful 🎉
==> Deploying...
==> Running 'uv run mcp_server.py'
...
...
...
==> Available at your primary URL https://remote-mcp-syp1.onrender.com==> Available at your primary URL https://remote-mcp-syp1.onrender.com
...
Detected service running on port 10000
...
...

La dirección vital que necesita es la marcada como la URL principal. En nuestro caso, esto es https://remote-mcp-syp1.onrender.com

Probar nuestro servidor MCP remoto

Podemos hacer esto de la misma manera que probamos la ejecución local, es decir, usando curl. Primero, verifique la temperatura máxima, esta vez Chicago. Tenga en cuenta el cambio de URL a nuestro nuevo remoto.

$ curl --ssl-no-revoke -sN -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_historical_weekly_high","arguments":{"city_name":"Chicago"}}}' https://remote-mcp-syp1.onrender.com/temp/mcp/|sed -n '/^data:/{s/^data: //;p}'

¿Y nuestra salida?

{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"95.4"}],"isError":false}}

Los ojos agudos entre ustedes pueden haber notado que hemos incluido una bandera adicional (-SSL-No-Revoke) En el comando curl anterior en comparación con el que usamos localmente. Esto se debe simplemente a una peculiaridad en la forma en que Curl funciona en Windows. Si está utilizando WSL2 para Windows o Linux, no necesita esta bandera adicional.

A continuación, probamos nuestro control remoto del Premio Nobel. Esta vez para la química en 2024.

$  $ curl --ssl-no-revoke -sN \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"nobel_checker","arguments":{"year":2024,"subject":"Chemistry"}}}' \
'https://remote-mcp-syp1.onrender.com/prize/mcp/' | sed -n '/^data:/{s/^data: //;p}'

Y la salida?

{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"David Baker"},{"type":"text","text":"Demis Hassabis"},{"type":"text","text":"John Jumper"}],"isError":false}}

Si desea intentar acceder al servidor MCP a través del código en lugar de usar Curl, aquí hay algún ejemplo de Python que ilustra llamar al control remoto nobel_checker herramienta.

import requests
import json
import ssl
from urllib3.exceptions import InsecureRequestWarning
from urllib3 import disable_warnings

# Disable SSL warnings (equivalent to --ssl-no-revoke)
disable_warnings(InsecureRequestWarning)

def call_mcp_server(url, method, tool_name, arguments, request_id=1):
    """
    Call a remote MCP server
    
    Args:
        url (str): The MCP server endpoint URL
        method (str): The JSON-RPC method (e.g., "tools/call")
        tool_name (str): Name of the tool to call
        arguments (dict): Arguments to pass to the tool
        request_id (int): JSON-RPC request ID
    
    Returns:
        dict: Response from the MCP server
    """
    
    # Prepare headers
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream"
    }
    
    # Prepare JSON-RPC payload
    payload = {
        "jsonrpc": "2.0",
        "id": request_id,
        "method": method,
        "params": {
            "name": tool_name,
            "arguments": arguments
        }
    }
    
    try:
        # Make the request with SSL verification disabled
        response = requests.post(
            url,
            headers=headers,
            json=payload,
            verify=False,  # Equivalent to --ssl-no-revoke
            stream=True   # Support for streaming responses
        )
        
        # Check if the request was successful
        response.raise_for_status()
        
        # Try to parse as JSON first
        try:
            return response.json()
        except json.JSONDecodeError:
            # If not JSON, return the text content
            return {"text": response.text}
            
    except requests.exceptions.RequestException as e:
        return {"error": f"Request failed: {str(e)}"}

# Example usage
if __name__ == "__main__":
    
    result = call_mcp_server(
        url="https://remote-mcp-syp1.onrender.com/prize/mcp/",
        method="tools/call",
        tool_name="prize_checker",
        arguments={"year": 2024, "subject": "Chemistry"}
    )
    print("\MCP Tool Call Response:")
    print(json.dumps(result, indent=2))

La salida es.

\MCP Tool Call Response:
{
  "text": "event: message\r\ndata: {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"David Baker\"},{\"type\":\"text\",\"text\":\"Demis Hassabis\"},{\"type\":\"text\",\"text\":\"John Jumper\"}],\"isError\":false}}\r\n\r\n"
}

Resumen

Este artículo presenta cómo escribir, probar e implementar Su servidor de contexto de modelo HTTP remoto y transmisible (MCP) en la nube, lo que permite a cualquier cliente MCP acceder a las funciones (herramientas) de forma remota.

Le mostré cómo codificar algunas funciones útiles útiles: un verificador de premios Nobel y una herramienta de información de temperatura de la ciudad. Después de probarlos localmente utilizando el rizo Comando para asegurarse de que funcionen como se esperaba, los convertimos en herramientas MCP y codificamos un servidor MCP. Después de implementar y probar con éxito el servidor MCP localmente, observamos cómo implementar nuestro servidor en la nube.

Para ese propósito, demostré cómo usar Render, una plataforma de alojamiento en la nube, y lo guié a través de los pasos de registrarse e implementar (de forma gratuita) nuestra aplicación MCP Server. Luego usamos Curl para probar el servidor remoto, confirmando que estaba funcionando como se esperaba.

Finalmente, también proporcioné algún código de Python que puede usar para probar el servidor MCP.

Siéntase libre de probar mi servidor MCP en Render para usted. Tenga en cuenta que debido a que está en el nivel libre, el servidor gira después de un período de inactividad, lo que puede dar como resultado un retraso de 30-60 segundos en la recuperación de los resultados.