# Descubrimiento del servidor

Endpoints .well-known con los que los clientes OAuth, OpenID Connect y MCP se configuran solos: qué devuelve cada uno y para qué sirve.

Los endpoints de descubrimiento permiten que tu cliente se configure solo: en lugar de escribir a mano cada URL, la lee de un documento estándar. Las bibliotecas OpenID Connect (Auth.js, Authlib, oidc-client-ts) solo necesitan el emisor, `https://os.utilia.ai`, y los clientes MCP llegan por aquí al servidor de autorización. Todos son públicos: no envíes `X-Api-Key` ni `Authorization`.

Viven en la raíz del dominio, fuera de `/api`, porque así lo exigen las especificaciones: `https://os.utilia.ai/.well-known/...`.

Así los encadena un cliente MCP que solo conoce la dirección del servidor, por ejemplo `https://os.utilia.ai/api/mcp/crm`:

**Cómo se configura solo un cliente MCP**

Participantes: Cliente MCP, Servidor MCP y Servidor de autorización.

1. Cliente MCP → Servidor MCP: `POST /api/mcp/crm`. Primera llamada, todavía sin token.
2. Servidor MCP → Cliente MCP (respuesta): 401 con WWW-Authenticate. La cabecera lleva resource_metadata con la dirección de los metadatos de ese servidor.
3. Cliente MCP → Servidor de autorización: `GET /.well-known/oauth-protected-resource?resource=...`. Lee los scopes del servidor y quién autoriza (authorization_servers).
4. Cliente MCP → Servidor de autorización: `GET /.well-known/oauth-authorization-server`. Obtiene las direcciones de registro, autorización y token.
5. Cliente MCP → Servidor de autorización: `POST /api/oauth/register`. Registro dinámico: recibe su client_id.
6. Servidor de autorización (por su cuenta): Autorización con PKCE y resource, y canje del código por tokens.
7. Cliente MCP → Servidor MCP: `POST /api/mcp/crm con Authorization: Bearer`. Ya puede llamar a las herramientas en nombre de la persona.

_Los dos servidores son de UTILIA OS; el cliente solo necesita conocer la dirección del servidor MCP._

El paso de autorización está contado en [Flujo de autorización](https://os.utilia.ai/docs/developers/oauth/authorization-flow).

## Metadatos del servidor de autorización (RFC 8414)

**`GET /.well-known/oauth-authorization-server`**

Metadatos del servidor de autorización

Devuelve las direcciones de autorización, token, registro, revocación e introspección y lo que admite el servidor. En el ejemplo, `scopes_supported` está recortado: la respuesta real trae el catálogo entero de scopes, incluidos los conjuntos (`crm:read`, `finance:full`...).

```json
{
  "issuer": "https://os.utilia.ai",
  "authorization_endpoint": "https://os.utilia.ai/api/oauth/authorize",
  "token_endpoint": "https://os.utilia.ai/api/oauth/token",
  "registration_endpoint": "https://os.utilia.ai/api/oauth/register",
  "revocation_endpoint": "https://os.utilia.ai/api/oauth/revoke",
  "introspection_endpoint": "https://os.utilia.ai/api/oauth/introspect",
  "scopes_supported": ["openid", "profile", "email", "crm:clients:view", "dev:projects:view", "..."],
  "response_types_supported": ["code"],
  "response_modes_supported": ["query"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"],
  "code_challenge_methods_supported": ["S256"],
  "prompt_values_supported": ["none", "login", "select_account", "consent"],
  "authorization_response_iss_parameter_supported": true,
  "service_documentation": "https://os.utilia.ai/docs/developers/mcp",
  "logo_uri": "https://os.utilia.ai/icons/icon-192x192.png",
  "service_name": "UTILIA OS"
}
```

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `issuer` | `string` | Sí | Emisor. Es el valor del claim iss del id_token. |
| `authorization_endpoint` | `string` | Sí | Donde empieza la autorización (redirección de la persona). |
| `token_endpoint` | `string` | Sí | Canje del código y renovación de tokens. |
| `registration_endpoint` | `string` | Sí | Registro dinámico de clientes (RFC 7591). |
| `revocation_endpoint` | `string` | Sí | Revocación de tokens (RFC 7009). |
| `introspection_endpoint` | `string` | Sí | Introspección de access tokens (RFC 7662). |
| `scopes_supported` | `string[]` | Sí | Catálogo de scopes. Ver Scopes y recursos. |
| `code_challenge_methods_supported` | `string[]` | Sí | Solo S256. |
| `prompt_values_supported` | `string[]` | No | Valores admitidos de prompt. |
| `authorization_response_iss_parameter_supported` | `boolean` | No | true: la respuesta de autorización, de éxito y de error, lleva iss con el emisor (RFC 9207). |
| `logo_uri` | `string` | No | Icono de UTILIA OS de 192 x 192 píxeles. |

## Configuración de OpenID Connect

**`GET /.well-known/openid-configuration`**

Metadatos de OpenID Connect Discovery 1.0

Es el documento anterior con los campos de OpenID Connect añadidos:

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `jwks_uri` | `string` | Sí | <https://os.utilia.ai/api/oauth/jwks>: claves públicas para verificar el id_token. |
| `userinfo_endpoint` | `string` | Sí | <https://os.utilia.ai/api/oauth/userinfo> |
| `end_session_endpoint` | `string` | Sí | <https://os.utilia.ai/api/oauth/logout>: cierre de sesión iniciado por tu aplicación. |
| `id_token_signing_alg_values_supported` | `string[]` | Sí | RS256. |
| `subject_types_supported` | `string[]` | Sí | public. |
| `claims_supported` | `string[]` | Sí | sub, iss, aud, exp, iat, name, given_name, family_name, picture, email, email_verified, org_id, at_hash y nonce. |

Lee `jwks_uri` de aquí en lugar de escribirlo en tu código: así un cambio de claves no te obliga a tocar nada.

## Metadatos de cada recurso protegido (RFC 9728)

Cada servidor MCP de UTILIA OS es un recurso protegido con su propia lista de scopes. Los clientes MCP leen estos documentos para saber qué pedir.

**`GET /.well-known/oauth-protected-resource`**

Metadatos del recurso protegido; admite ?resource=

Con `?resource=` y la dirección de un servidor MCP devuelve los metadatos de ese servidor. Sin el parámetro, o con una dirección que no es de ningún servidor, devuelve los generales: el propio origen como recurso (`https://os.utilia.ai`) y solo `openid`, `profile` y `email`. Hasta el 20 de octubre de 2026 anunciaban la dirección única `/api/mcp` con el catálogo entero; esa dirección se retiró y hoy responde 410.

```json
{
  "resource": "https://os.utilia.ai/api/mcp/crm",
  "authorization_servers": ["https://os.utilia.ai"],
  "scopes_supported": ["crm:clients:view", "crm:leads:view", "support:tickets:view", "dev:calendar:view", "org:plan:view", "..."],
  "bearer_methods_supported": ["header"],
  "resource_documentation": "https://os.utilia.ai/dashboard/docs/developers/mcp",
  "logo_uri": "https://os.utilia.ai/icons/icon-192x192.png",
  "resource_name": "UTILIA OS - CRM"
}
```

Cada servidor tiene además un atajo con el mismo documento:

| Servidor | Atajo | `resource` anunciado | Qué anuncia |
| - | - | - | - |
| CRM | `/.well-known/mcp-crm.json` | `/api/mcp/crm` | `crm:*`, tickets de soporte, calendario, gestor de archivos, credenciales de API, la lectura de proveedores y los granulares de administración de la organización |
| Proyectos | `/.well-known/mcp-work.json` | `/api/mcp/work` | `dev:*`, tickets de soporte e `integrations:*` |
| Proyectos (alias) | `/.well-known/mcp-dev.json` | `/api/mcp/dev` | Lo mismo que `mcp-work.json` |
| Finanzas | `/.well-known/mcp-finance.json` | `/api/mcp/finance` | `finance:*`, `crm:invoices:*`, `crm:treasury:*`, `crm:fiscal:*` y la lectura de clientes y tareas |
| Marketing | `/.well-known/mcp-marketing.json` | `/api/mcp/marketing` | `marketing:*`, dominios de envío y las lecturas del CRM que necesita la vista previa de segmentos |
| Chat | `/.well-known/mcp-chat.json` | `/api/mcp/chat` | Una lista cerrada de `chat:*` |
| RRHH | `/.well-known/mcp-rrhh.json` | `/api/mcp/rrhh` | `hr:*` |

Todos anuncian además los transversales que su servidor necesita: el plan contratado (`org:plan:*`), los avisos (`org:notifications:*`) y, salvo CRM y Proyectos, que ya los incluyen, los tres de tickets propios (`support:tickets:view`, `create` y `update`).

Dos casos en los que el documento anuncia menos de lo que podría pedirse, a propósito:

- **Chat** no anuncia `chat:messages:send`, `chat:events:subscribe` ni `chat:agents:manage:external`, ni los conjuntos `chat:write` y `chat:full`, que los incluyen: el servidor MCP de chat lee y organiza canales, pero no escribe mensajes. Publicar mensajes es cosa de la API externa (`/api/external/v1/chat`) con `X-Api-Key`.
- **Marketing** no anuncia ningún `finance:*`. Por eso sus operaciones de negocio originado por campaña devuelven recuentos, pero no importes. Lo explica [Servidor MCP Marketing](https://os.utilia.ai/docs/developers/mcp/marketing).

`/api/mcp/work` es la dirección pública del servidor de Proyectos; los tokens guardan como destinatario `/api/mcp/dev`, así que un mismo token sirve para las dos direcciones.

## Otros documentos

**`GET /.well-known/jwks.json`**

Claves públicas (JWKS) para verificar el id_token

Devuelve las mismas claves que `GET /api/oauth/jwks`. Solo lo necesitas si verificas tú la firma del `id_token`.

**`GET /.well-known/resource-server.json`**

Variante antigua de los metadatos del recurso protegido

Devuelve lo mismo que `/.well-known/oauth-protected-resource` sin parámetro y existe solo para clientes antiguos. En una integración nueva, usa `oauth-protected-resource` con `resource`.

## Ejemplos

### curl

```bash
# Servidor de autorización
curl -s 'https://os.utilia.ai/.well-known/oauth-authorization-server' | jq .

# OpenID Connect
curl -s 'https://os.utilia.ai/.well-known/openid-configuration' | jq .

# Recurso protegido del servidor de CRM
curl -s 'https://os.utilia.ai/.well-known/oauth-protected-resource?resource=https://os.utilia.ai/api/mcp/crm' | jq .

# Atajo del servidor de Finanzas
curl -s 'https://os.utilia.ai/.well-known/mcp-finance.json' | jq .
```

### JavaScript

```javascript
const oidc = await fetch(
  "https://os.utilia.ai/.well-known/openid-configuration",
).then((r) => r.json());

console.log(oidc.authorization_endpoint); // https://os.utilia.ai/api/oauth/authorize
console.log(oidc.jwks_uri);               // https://os.utilia.ai/api/oauth/jwks

const crm = await fetch(
  "https://os.utilia.ai/.well-known/oauth-protected-resource?resource=https://os.utilia.ai/api/mcp/crm",
).then((r) => r.json());

console.log(crm.scopes_supported);
```

### Python

```python
import requests

oidc = requests.get("https://os.utilia.ai/.well-known/openid-configuration").json()
print(oidc["authorization_endpoint"])  # https://os.utilia.ai/api/oauth/authorize
print(oidc["jwks_uri"])                # https://os.utilia.ai/api/oauth/jwks

work = requests.get("https://os.utilia.ai/.well-known/mcp-work.json").json()
print(work["scopes_supported"])
```

Estos documentos admiten hasta 600 peticiones por minuto y dirección IP. Aun así, guárdalos en caché en tu cliente: cambian muy poco.

Siguiente paso: [Flujo de autorización](https://os.utilia.ai/docs/developers/oauth/authorization-flow).
