mirror of
https://github.com/suitenumerique/docs.git
synced 2026-09-10 11:47:52 +02:00
🔧(keycloak) configure realm for the MCP server
Add to the dev realm:
- `docs-mcp-client`, the public PKCE client MCP clients authenticate
with, with optional scopes `docs:documents:{search,read,create}` and
`docs-mcp`.
- `docs-api`, the confidential client Django uses as introspection
credentials (`OIDC_RS_CLIENT_ID/SECRET`).
- the `docs:documents:*` consent scopes and a `docs-mcp` audience-mapper
scope that stamps the `docs-mcp` audience only when requested.
Point the resource-server env at the `impress` realm and the new
clients, allow the `docs-mcp-client` audience, and grant users
`offline_access` for refresh tokens.
This commit is contained in:
+135
-1
@@ -56,7 +56,7 @@
|
||||
"value": "impress"
|
||||
}
|
||||
],
|
||||
"realmRoles": ["user"]
|
||||
"realmRoles": ["user", "offline_access"]
|
||||
},
|
||||
{
|
||||
"username": "user-e2e-chromium",
|
||||
@@ -834,6 +834,88 @@
|
||||
"offline_access",
|
||||
"microprofile-jwt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"clientId": "docs-mcp-client",
|
||||
"name": "Docs MCP development client",
|
||||
"description": "Public OAuth client used by MCP clients (MCP Inspector, Claude Code, Codex CLI ...) to authenticate against the docs-mcp server. Authorization Code + PKCE only, no client secret.",
|
||||
"rootUrl": "",
|
||||
"adminUrl": "",
|
||||
"baseUrl": "",
|
||||
"surrogateAuthRequired": false,
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": true,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": ["*"],
|
||||
"webOrigins": ["+"],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": true,
|
||||
"standardFlowEnabled": true,
|
||||
"implicitFlowEnabled": false,
|
||||
"directAccessGrantsEnabled": false,
|
||||
"serviceAccountsEnabled": false,
|
||||
"publicClient": true,
|
||||
"frontchannelLogout": false,
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"pkce.code.challenge.method": "S256",
|
||||
"post.logout.redirect.uris": "+"
|
||||
},
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": -1,
|
||||
"defaultClientScopes": [
|
||||
"web-origins",
|
||||
"acr",
|
||||
"roles",
|
||||
"profile",
|
||||
"email"
|
||||
],
|
||||
"optionalClientScopes": [
|
||||
"offline_access",
|
||||
"docs:documents:search",
|
||||
"docs:documents:read",
|
||||
"docs:documents:create",
|
||||
"docs-mcp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"clientId": "docs-api",
|
||||
"name": "Docs API resource server",
|
||||
"description": "Represents the Django Docs API. Confidential client whose credentials Django uses to call Keycloak's token introspection endpoint (OIDC_RS_CLIENT_ID/SECRET). See documentation/mcp_server.md.",
|
||||
"rootUrl": "",
|
||||
"adminUrl": "",
|
||||
"baseUrl": "",
|
||||
"surrogateAuthRequired": false,
|
||||
"enabled": true,
|
||||
"alwaysDisplayInConsole": true,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"secret": "ThisIsAnExampleKeyForDevPurposeOnly",
|
||||
"redirectUris": [],
|
||||
"webOrigins": [],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": false,
|
||||
"consentRequired": false,
|
||||
"standardFlowEnabled": false,
|
||||
"implicitFlowEnabled": false,
|
||||
"directAccessGrantsEnabled": false,
|
||||
"serviceAccountsEnabled": false,
|
||||
"publicClient": false,
|
||||
"frontchannelLogout": false,
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {},
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
"fullScopeAllowed": false,
|
||||
"nodeReRegistrationTimeout": -1,
|
||||
"defaultClientScopes": [
|
||||
"web-origins",
|
||||
"acr",
|
||||
"roles",
|
||||
"profile",
|
||||
"email"
|
||||
],
|
||||
"optionalClientScopes": []
|
||||
}
|
||||
],
|
||||
"clientScopes": [
|
||||
@@ -1359,6 +1441,58 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "docs:documents:search",
|
||||
"description": "Search the documents accessible to the authenticated user",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "Search your Docs documents"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "docs:documents:read",
|
||||
"description": "Read documents accessible to the authenticated user",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "Read your Docs documents"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "docs:documents:create",
|
||||
"description": "Create documents on behalf of the authenticated user",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "true",
|
||||
"display.on.consent.screen": "true",
|
||||
"consent.screen.text": "Create documents in Docs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "docs-mcp",
|
||||
"description": "Adds the docs-mcp audience to the access token. Required for the MCP server to accept the token; only added when this scope is explicitly requested.",
|
||||
"protocol": "openid-connect",
|
||||
"attributes": {
|
||||
"include.in.token.scope": "false",
|
||||
"display.on.consent.screen": "false"
|
||||
},
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "docs-mcp audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.custom.audience": "docs-mcp",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"defaultDefaultClientScopes": [
|
||||
|
||||
@@ -52,13 +52,20 @@ OIDC_REDIRECT_ALLOWED_HOSTS="localhost:8083,localhost:3000"
|
||||
OIDC_AUTH_REQUEST_EXTRA_PARAMS={"acr_values": "eidas1"}
|
||||
|
||||
# Resource Server Backend
|
||||
OIDC_OP_URL=http://localhost:8083/realms/docs
|
||||
OIDC_OP_INTROSPECTION_ENDPOINT = http://nginx:8083/realms/docs/protocol/openid-connect/token/introspect
|
||||
# Also validates tokens the MCP server forwards from its callers (see core/mcp_api and
|
||||
# documentation/mcp_server.md): the user's Keycloak token is introspected here with
|
||||
# OIDC_RS_CLIENT_ID/SECRET (the `docs-api` confidential client) as credentials.
|
||||
# OIDC_RS_AUDIENCE_CLAIM stays at its "client_id" default: it's process-wide (shared with
|
||||
# the disabled external_api feature), and new resource-server callers just get added to
|
||||
# OIDC_RS_ALLOWED_AUDIENCES. For a forwarded token that client_id is the public client the
|
||||
# user authenticated as (docs-mcp-client).
|
||||
OIDC_OP_URL=http://localhost:8083/realms/impress
|
||||
OIDC_OP_INTROSPECTION_ENDPOINT=http://nginx:8083/realms/impress/protocol/openid-connect/token/introspect
|
||||
OIDC_RESOURCE_SERVER_ENABLED=False
|
||||
OIDC_RS_CLIENT_ID=docs
|
||||
OIDC_RS_CLIENT_ID=docs-api
|
||||
OIDC_RS_CLIENT_SECRET=ThisIsAnExampleKeyForDevPurposeOnly
|
||||
OIDC_RS_AUDIENCE_CLAIM="client_id" # The claim used to identify the audience
|
||||
OIDC_RS_ALLOWED_AUDIENCES=""
|
||||
OIDC_RS_ALLOWED_AUDIENCES=docs-mcp-client
|
||||
OIDC_RS_SCOPES=docs:documents:search,docs:documents:read,docs:documents:create
|
||||
|
||||
# Store OIDC tokens in the session. Needed by search/ endpoint.
|
||||
# OIDC_STORE_ACCESS_TOKEN=True
|
||||
|
||||
Reference in New Issue
Block a user