Skip to main content
Version: 3.1.x

MCP

🚧Documentation Under Construction

We are actively working to improve this documentation. The content you see here may be incomplete, subject to change, or may not fully reflect the current state of the feature. We appreciate your understanding as we continue to enhance our docs.

The Brightspot AI plugin can expose CMS content over the Model Context Protocol (MCP), letting external AI clients—IDE assistants, chat apps, custom agents—search, read, create, update, and delete content through a tool-calling interface.

The MCP server is a separate module that runs inside the Brightspot web application. It is implemented as an ApiEndpoint, so it inherits Brightspot's existing API client, credential, and permission model. The same @ToolMethod-annotated tools used by in-process agents (Ask AI, Create with AI) are reused as MCP tools.

How it works

  • The mcp module exposes MCP through CustomApiEndpoint (display name Custom API) paired with McpApiHandler (display name MCP). The endpoint owns paths and the authenticator chain; the handler owns the protocol.
  • A servlet filter (McpApiEndpointFilter) starts a stateless MCP server on application init. It discovers every concrete ToolGroup on the classpath, resolves each @ToolMethod-annotated method into a tool, and registers it with the server.
  • The server advertises tools only—prompts and resources are disabled. Each tool name is namespaced as <GroupName>_<methodName> (for example, Content_search).
  • The endpoint authenticates the request and then delegates to the MCP handler, which dispatches the tool call. The first authenticator that resolves a user sets it as the current ToolUser.
  • Tool return values become the MCP structuredContent payload. Scalars and collections are wrapped as {"result": ...}. Exceptions are returned as an error result with the stack trace as text content.

Installation

Add the mcp artifact alongside the core AI plugin:

<!-- Requires Brightspot 4.8 or later. -->
<dependency>
<groupId>com.brightspot.ai</groupId>
<artifactId>mcp</artifactId>
<version>3.1.0</version>
</dependency>

This pulls in the upstream MCP SDK (io.modelcontextprotocol.sdk:mcp-core plus the Jackson JSON transport at runtime) and registers the MCP endpoint and built-in Content tool group.

Setting up the endpoint

The MCP server is exposed through a Brightspot API endpoint, configured in the CMS like any other endpoint.

To create an MCP endpoint:

  1. Click > Admin > APIs.
  2. Create a new API endpoint and select Custom API as the type.
  3. In the Paths field, add one or more URL paths to serve MCP traffic at (for example, /mcp).
  4. Add one or more Authenticators (typically OAuth and/or API Key). Endpoints with no authenticators reject every request with 401 Unauthorized.
  5. From the Handler list, select MCP.
  6. Save.

The endpoint is now reachable at the configured paths on your application host. Point MCP clients at the full URL, including scheme and host.

Authentication

CustomApiEndpoint runs the configurable ApiAuthenticator chain. Two built-in authenticators ship with the plugin:

  • OAuth (OAuthApiAuthenticator) — Authorization: Bearer <JWT>. The JWT is issued by Brightspot's OAuth 2.1 authorization server (see OAuth). Its sub claim names the end user; tool calls run with that user's CMS permissions.
  • API Key (ApiKeyApiAuthenticator) — Authorization: Bearer <opaque-token> or X-API-Key: <token>. The client is resolved as a Brightspot ApiClient and tool calls run as the client's configured service user (see Access control).

When more than one is configured the chain runs in order; the first authenticator that matches the request wins.

note

When the Authorization header carries a perimeter HTTP Basic credential — for example, a lower-environment access gate that occupies the standard Authorization slot — both authenticators read the bearer token from an X-Auth-Token request header instead. The fallback only applies when Authorization uses the Basic scheme; clients in environments without the perimeter gate should continue to send Authorization: Bearer <token>.

The standard OAuth WWW-Authenticate: Bearer resource_metadata=... challenge is not extended to advertise this fallback, so OAuth autodiscovery does not work behind a Basic gate. Clients in these environments must be configured out-of-band to send their token via X-Auth-Token (alongside the Basic credential they already have for the gate).

Invalid credentials produce 401 Unauthorized with each configured authenticator's challenge as a WWW-Authenticate header — for OAuth that's the RFC 9728 Bearer resource_metadata=... pointer to the protected-resource metadata document. Valid credentials that lack endpoint or permission access produce 403 Forbidden.

For external MCP clients (IDE assistants, hosted chat apps), configure the OAuth authenticator — clients discover the authorization server from the challenge, register dynamically, and issue per-user tokens with no manual credential sharing. The API-key path is useful for in-house service-to-service calls where a shared credential is acceptable.

OAuth

OAuth support is provided by the cms-oauth-server module that ships with Brightspot CMS. Add it to your application alongside the mcp dependency to enable the OAuth authenticator and discovery endpoints:

<!-- Requires Brightspot 4.8 or later. -->
<dependency>
<groupId>com.psddev</groupId>
<artifactId>cms-oauth-server</artifactId>
<version>3.1.0</version>
</dependency>

It is an OAuth 2.1 authorization server supporting the authorization-code flow with PKCE (S256), the client-credentials flow, refresh tokens with rotation and replay detection, dynamic client registration (RFC 7591), token revocation (RFC 7009), resource indicators (RFC 8707), and automatic signing-key rotation.

Discovery endpoints (RFC 8414, RFC 9728):

PathPurpose
/.well-known/oauth-authorization-serverAuthorization server metadata.
/.well-known/oauth-protected-resource/<path>Protected resource metadata for the MCP endpoint at <path>.
/.well-known/jwks.jsonJSON Web Key Set for verifying issued JWTs.

OAuth endpoints:

PathPurpose
/_oauth/authorizeAuthorization endpoint; renders the consent screen.
/_oauth/tokenToken endpoint; handles all supported grant types.
/_oauth/registerDynamic client registration.
/_oauth/revokeRefresh-token revocation.

A typical MCP client flow:

  1. The client makes an unauthenticated request to the MCP endpoint and receives 401 with the resource_metadata challenge.
  2. The client fetches the metadata documents to discover the authorization server.
  3. The client registers itself at /_oauth/register (or uses a pre-provisioned ApiClient).
  4. The client starts the authorization-code flow at /_oauth/authorize with code_challenge (S256) and resource=<MCP endpoint URL>.
  5. The CMS-authenticated user approves the consent screen (or reuses a remembered consent).
  6. The client exchanges the code at /_oauth/token and calls the MCP endpoint with the resulting JWT.

The OAuthPlugin record configures the issuer URL, code/token/key TTLs, and the key rotation interval. When the issuer is unset, the subsystem derives it from the current request's scheme and host — fine for development, but set it explicitly behind reverse proxies or for tokens minted outside a web request.

Toggle on the Disable DCR setting on the same record to restrict access to operator-provisioned clients only. When disabled, /_oauth/register returns 404 and the registration_endpoint field is omitted from the authorization-server metadata, signaling to clients that dynamic registration is unavailable.

To pre-provision an MCP client without OAuth:

  1. Click > Admin > APIs.
  2. Create or open an API Client.
  3. Generate a token for the client and grant it access to the MCP endpoint.
  4. Set the Service User field on the client (see Access control) — this is the tool user the client acts as.
  5. Share the token with the MCP client out of band.

Access control

Every ApiClient carries a Service User field (via the ApiClientExtra modification). It's the tool user the client acts as for non-OAuth authentication — API key, opaque bearer, or the OAuth client_credentials grant. Tool calls run with that user's site and content type permissions, the same model used by the in-CMS tool UI.

Configure access by creating a dedicated service ToolUser (or reusing an existing one) with exactly the role, sites, and content types the MCP client should see, then point the API client at it. Built-in and custom ToolGroup implementations that go through ToolRequest automatically pick up these restrictions—no extra wiring is required.

Each API client carries its own service user, so different manually-provisioned clients can run with different CMS permissions and audit trails. OAuth-issued tokens resolve their user from the JWT's sub claim instead and don't consult the service user.

tip

Treat MCP credentials like any other API token. A client inherits everything its service user can do; create a narrowly scoped user rather than reusing a privileged administrator.

Built-in tools

The mcp module ships one tool group, Content (ContentToolGroup), providing CRUD and search over Brightspot content. All tools are namespaced Content_*:

ToolHintsPurpose
Content_searchTyperead-only, idempotentFind names of CMS content types the user can read, by partial match.
Content_getTyperead-only, idempotentGet the JSON Schema for a content type.
Content_getFieldValuesread-only, idempotentList allowed values for a field with a value generator.
Content_searchTextuallyread-only, idempotentKeyword search across content.
Content_searchSemanticallyread-only, idempotentEmbedding-based search across content.
Content_getread-only, idempotentFetch a single content item by ID.
Content_generateIdsread-onlyGenerate fresh UUIDs for new content.
Content_createwriteCreate new content of a given type.
Content_updatewriteUpdate existing content by ID.
Content_deletewrite, destructiveDelete content by ID.

Content_searchType returns only CMS content types the caller's user has permission to read, so an agent never discovers a type it cannot query—a missing type then reads as "no access" rather than "no data." Embedded and other non-content Record types—the $ref targets in a Content_getType schema—are not listed here; resolve them by exact name through Content_getType, which is not permission-filtered.

The group also publishes a usage guide to the MCP server's instructions payload so that clients can route tool calls correctly without trial and error. See ContentToolGroup#getInstructions for the current text.

Content_searchSemantically requires a configured text embedding generator (see Configuration). The other tools work without one.

Adding custom tools

Any concrete subclass of ToolGroup on the classpath is picked up automatically. Tools defined this way are available both to the in-process agents and to MCP clients.

1
@NullMarked
2
public class WeatherToolGroup extends ToolGroup {
3
4
@Override
5
public String getName() {
6
return "Weather";
7
}
8
9
@Override
10
public @Nullable String getInstructions() {
11
return "Use Weather_* tools for current conditions and forecasts.";
12
}
13
14
@ToolMethod(
15
title = "Get the current temperature for a city",
16
readOnly = true,
17
destructive = false,
18
idempotent = false,
19
description = "Returns the current temperature in Celsius for the given city.")
20
public double currentTemperature(
21
@ToolParam(description = "City name, e.g. `San Francisco`.") String city) {
22
23
return WeatherApi.lookup(city).temperatureCelsius();
24
}
25
}

Notes:

  • The group's getName() becomes the tool name prefix (Weather_currentTemperature here).
  • Per-method behavioral hints (readOnly, destructive, idempotent) on @ToolMethod are forwarded to MCP clients, which may use them to auto-approve calls or prompt for confirmation.
  • Parameter schemas are derived from @ToolParam annotations and the method signature. For non-standard schemas, supply a SchemaSupplier via @ToolParam.
  • Return values are serialized as structured content. Scalars and collections are auto-wrapped as {"result": ...}; return a Map<String, Object> for richer payloads.
  • Throwing from a tool method produces an MCP error result; the stack trace is included as text content.

For deeper internals—Tool, RunnableTool, ToolGroup discovery, and the agentic chat loop that consumes the same tools—see Architecture overview.