De Azure Function a servidor MCP para Copilot Studio
Si ya tienes lógica de negocio desplegada en Azure Functions, no necesitas reescribirla como servidor MCP para que un agente la use. Azure API Management (APIM) puede coger las operaciones REST que ya publicas y exponerlas como herramientas MCP, y Copilot Studio puede conectarse a ese servidor MCP como a cualquier otro.
En este post lo monto de principio a fin con un ejemplo pequeño pero completo, que responde a una pregunta muy concreta: "¿Me conviene salir a correr hoy en Barcelona?"
Todo el código está en GitHub: ricardPlana/SurvivingDynamics_MCPServer. Incluye la Function, la web, los contratos OpenAPI, las pruebas, la infraestructura en Bicep, el script de despliegue y una guía completa paso a paso.
La idea: tu Function no tiene que hablar MCP
La Function no implementa MCP. Expone cuatro operaciones HTTP normales y APIM se encarga del resto:
- APIM importa el contrato OpenAPI de la Function dos veces: una API pública para la web (
/weather) y otra protegida con suscripción para el agente (/copilot-weather). - Sobre la API del agente se crea un servidor MCP en APIM, eligiendo qué operaciones se convierten en herramientas.
- Copilot Studio se conecta a la URL
/mcpque genera APIM, por Streamable HTTP, con la clave de suscripción en una cabecera.
Arquitectura: la web y Copilot Studio comparten la misma Azure Function a través de API Management
¿Por qué me gusta este enfoque?
- Una sola lógica, dos consumidores. La web y el agente usan exactamente las mismas reglas. Si cambias un umbral, cambia para los dos.
- Sin SDK de MCP ni hosting nuevo. Lo que ya tienes en producción sirve tal cual.
- El gateway hace de frontera. Claves, productos, suscripciones, CORS, límites por IP y monitorización viven en APIM, no en el código ni en el prompt.
El ejemplo: una pregunta, cuatro herramientas
La Function (TypeScript, Flex Consumption) expone cuatro operaciones GET que consultan Open-Meteo:
| Operación | Para qué sirve |
|---|---|
/locations?query= | Devuelve hasta diez localidades con país y región para desambiguar |
/weather | Tiempo actual |
/forecast | Previsión diaria: mínima, máxima, lluvia, viento y estado del cielo |
/running-assessment | Aplica una regla explícita y explica si conviene correr |
La regla vive en la API, no en el prompt
Este es para mí el punto clave del diseño. La recomendación no la decide el modelo: la calcula la API con unos umbrales de demostración transparentes.
| Dato diario | Criterio |
|---|---|
| Temperatura mínima | Al menos 5 °C |
| Temperatura máxima | Como máximo 30 °C |
| Precipitación | Como máximo 2 mm |
| Viento máximo | Como máximo 25 km/h |
La respuesta devuelve la recomendación junto con la evidencia: motivos, umbrales, previsión y un aviso. Así el agente presenta datos verificables en lugar de inventarse criterios:
1{
2 "activity": "running",
3 "recommendation": "recommended",
4 "city": "Barcelona",
5 "country": "Spain",
6 "date": "2026-10-21",
7 "reasons": [],
8 "thresholds": {
9 "minimumTemperatureC": 5,
10 "maximumTemperatureC": 30,
11 "maximumPrecipitationMm": 2,
12 "maximumWindSpeedKmh": 25
13 },
14 "forecast": {
15 "minimumTemperatureC": 10.4,
16 "maximumTemperatureC": 21.7,
17 "precipitationMm": 0,
18 "maximumWindSpeedKmh": 12.6,
19 "condition": "Overcast"
20 },
21 "disclaimer": "A simple demo heuristic, not medical advice or an official safety warning."
22}Otro detalle importante: las ciudades ambiguas son explícitas. Si hay varias localidades con el mismo nombre, la API responde 409 city_ambiguous con sugerencias en lugar de elegir una al azar. El agente busca primero la localidad y después pasa city, country y admin1 por separado.
Paso 1: desplegar la base
El repositorio incluye un script de PowerShell que despliega toda la infraestructura:
az login
az bicep install
.\infra\deploy.ps1 -ResourceGroupName rg-sd-weather-demo -Location westeurope -PublisherEmail you@example.comEl script crea el almacenamiento, la Function en Flex Consumption con identidad administrada, la Static Web App y APIM (tier Developer). Después publica la Function y la web, e importa las dos APIs en APIM.
La Function usa authLevel: "function", pero la clave nunca llega al frontend ni al agente. Una policy de APIM la inyecta desde un named value secreto y aplica el límite por IP:
1<rate-limit-by-key calls="60" renewal-period="60" counter-key="@(context.Request.IpAddress)" />
2<set-query-parameter name="code" exists-action="override">
3 <value>{{function-key}}</value>
4</set-query-parameter>Lo que el script NO hace: crear el servidor MCP, conectar Copilot Studio ni aplicar el ajuste del paso 2. Esos pasos son manuales en el portal.
Paso 2: ajustar las plantillas de URL (el detalle que nadie te cuenta)
Aquí me encontré el problema más interesante del proyecto. APIM descubría las herramientas sin problemas, pero componía mal las llamadas con parámetros opcionales. La Function recibía valores como city=Barcelona?country=Spain, y la previsión fallaba con invalid_date aunque la fecha fuera correcta. La misma llamada hecha directamente por REST funcionaba perfectamente.
La solución que verifiqué fue incluir todos los parámetros en la plantilla de URL de cada operación, solo en la API de Copilot. En APIM → APIs → Weather for Copilot Studio → Design → Frontend:
| Operación | Plantilla |
|---|---|
| Tiempo actual | /weather?city={city}&country={country}&admin1={admin1} |
| Previsión | /forecast?city={city}&date={date}&country={country}&admin1={admin1} |
| Running | /running-assessment?city={city}&date={date}&country={country}&admin1={admin1} |
| Búsqueda (sin cambios) | /locations?query={query} |
Si country y admin1 quedan duplicados en Query parameters, elimina esas entradas y conserva las de Template parameters.
El coste de esta solución: esos campos pasan a ser obligatorios en la API del agente. Por eso las instrucciones le piden buscar siempre la localidad primero. La API pública y la Function no cambian. Y ojo: si vuelves a importar la API, tendrás que repetir el ajuste.
Paso 3: crear el servidor MCP en APIM
En la instancia de APIM, ve a APIs → MCP Servers → Create MCP server → Expose an API as an MCP server:
APIM: menú Create MCP server con la opción Expose an API as an MCP server
Selecciona la API Weather for Copilot Studio y las operaciones que quieres exponer como herramientas. Dale un nombre (weather-planning-mcp) y una descripción clara (el agente la usa para entender para qué sirve el servidor), y asígnalo a un producto:
Formulario Expose an API as an MCP server: API, operaciones, nombre, descripción y producto
APIM genera una Server URL que termina en /mcp. Esa es la URL que necesita Copilot Studio, no la URL base de la API REST:
El servidor MCP creado en APIM con su Server URL
Importante: asocia el servidor MCP y la API REST de origen al mismo producto protegido, y activa Subscription required en los dos. Si proteges solo el MCP, la inicialización funciona pero cada llamada a una herramienta falla con
401.
Ten en cuenta también que el soporte MCP de APIM expone herramientas, no resources ni prompts.
Paso 4: una suscripción para el agente
En APIM → Subscriptions, crea una suscripción con ámbito en el producto al que asignaste el servidor MCP. No uses la built-in all-access subscription para un agente:
Creación de una suscripción de APIM con ámbito en el producto para Copilot Studio
Ten claras las dos credenciales: la clave de la Function se queda dentro de APIM, y la clave de suscripción es lo único que tendrá Copilot Studio.
Paso 5: conectar Copilot Studio
En tu agente, ve a Herramientas → Agregar una herramienta → Agregar → Model Context Protocol (MCP):
Copilot Studio: agregar una herramienta de tipo Model Context Protocol
Rellena el nombre, la descripción y la URL del servidor copiada de APIM. En autenticación, elige Clave API, ubicación Encabezado y nombre Ocp-Apim-Subscription-Key:
Formulario Agregar servidor de MCP con autenticación por clave API en la cabecera Ocp-Apim-Subscription-Key
Por último, crea la conexión con la clave de la suscripción del paso 4. La clave vive en la conexión: nunca en el prompt, en el repositorio ni en el frontend.
Creación de la conexión de Copilot Studio con la clave de suscripción de APIM
Paso 6: instrucciones del agente
APIM genera los nombres de las herramientas a partir de los resúmenes del OpenAPI. En mi caso fueron findMatchingCitiesAndRegions y evaluateWhetherForecastConditionsMeetTheDemoRunningCriteria. Estas son las instrucciones que usé:
Instrucciones del agente MCP-Weather en Copilot Studio
1Usa las herramientas MCP meteorológicas como fuente principal.
2
3Para resolver una ubicación, usa findMatchingCitiesAndRegions con el
4parámetro query. Conserva city, country y admin1 como campos separados
5y utiliza los valores devueltos por la búsqueda.
6
7Envía las fechas con formato YYYY-MM-DD. Resuelve "hoy" y "mañana"
8según la fecha actual y la zona horaria de la ubicación.
9
10Si recibes city_ambiguous, utiliza las ubicaciones devueltas para
11desambiguar; pregunta al usuario si falta información.
12
13Si una llamada con parámetros válidos devuelve un error técnico,
14no pruebes variantes arbitrarias ni inventes la causa. Informa del
15error sin presentar su contenido como datos meteorológicos.
16
17No sustituyas automáticamente la herramienta por búsquedas web.
18Si falla, indica que no has podido obtener la previsión del servicio.Las últimas instrucciones salen de la experiencia: cuando una herramienta fallaba, el agente probaba variantes y acababa respondiendo con una búsqueda web. El prompt delimita el comportamiento, pero no arregla URLs ni autenticación. Si quieres una demo centrada en las herramientas, desactiva también la búsqueda web como fuente de conocimiento.
El resultado
Le pregunto al agente "¿puedo salir a correr hoy en Barcelona?". Primero busca la localidad, después llama a la evaluación y responde con la previsión, los criterios aplicados y el aviso de que es una demostración:
Prueba en Copilot Studio: el agente usa las herramientas MCP y responde con la evaluación para correr
Validar por capas: un 200 no significa que funcione
La lección técnica más importante del proyecto: que el agente descubra las herramientas no demuestra que funcionen. Valida cada fase por separado:
| Fase | Qué demuestra | Qué no demuestra |
|---|---|---|
initialize | Acceso al endpoint y negociación MCP | Que la API REST de origen funcione |
tools/list | Descubrimiento y esquema de herramientas | Que los parámetros se construyan bien |
tools/call | Ejecución real | Éxito de negocio, si no inspeccionas el contenido |
En mis pruebas, APIM devolvía HTTP 200 e isError: false incluso cuando el texto de la herramienta contenía un 401 o un invalid_date. El éxito real se comprueba buscando los campos de la previsión o de la evaluación en content[].text.
Lecciones aprendidas
- Mantén las decisiones de negocio en la API. El modelo elige herramientas y explica resultados; la API valida, resuelve y calcula.
- Herramientas pequeñas y componibles. Buscar, consultar y evaluar son operaciones distintas que el agente encadena.
- Resuelve entidades antes de actuar. Una búsqueda explícita evita que el agente adivine qué "Barcelona" quieres.
- Diagnostica por capas. Antes de tocar validaciones o prompts, separa protocolo, autenticación y negocio, y compara siempre con la llamada REST directa.
- No ocultes errores. Ni la API ni el agente deben rellenar huecos con datos inventados.
Límites y costes
- APIM Developer es un tier facturable sin SLA de producción, y tarda un rato en aprovisionarse.
- La API pública de la web es anónima: CORS y el límite por IP no son autenticación.
- La versión gratuita de Open-Meteo es para uso no comercial y requiere atribución. Revisa sus términos antes de usarla en otro contexto.
- Los umbrales son de demostración, no consejo médico ni de seguridad.
- La configuración del portal (servidor MCP, plantillas, conexión) no está automatizada.
Cuando termines, borra el grupo de recursos dedicado (comprueba antes que solo contiene recursos de la demo) y elimina la conexión en Copilot Studio, porque borrar Azure no la retira:
az group delete --name rg-sd-weather-demoPruébalo tú
- Código: github.com/ricardPlana/SurvivingDynamics_MCPServer
- Guía completa en español (ejecución local, despliegue, validación y troubleshooting): docs/guia-completa.md
- Referencia de configuración de Azure: docs/azure-configuration.md
- Documentación oficial: exponer una API REST como servidor MCP en APIM y conectar un servidor MCP a Copilot Studio.
Si tienes APIs en Azure Functions, este patrón te permite ponerlas a disposición de tus agentes en una tarde, sin reescribir nada.