Model Context Protocol (MCP)
Model Context Protocol (MCP) is an open-source standard that enables AI applications to connect with external systems in a unified way.
MCP allows AI models to access and interact with external data sources, APIs, and services, empowering developers to build more powerful and flexible AI solutions that leverage a wide range of external capabilities.
Learn more on the official MCP website.
Exposing MCP Tools in Neos
Neos enables you to expose MCP tools securely and selectively. All Web APIs can be made available as MCP tools, allowing AI models to interact with them seamlessly.
Exposing an entity view as an MCP Tool
To expose an entity view as an MCP tool in Neos Studio:
- Open the entity view editor.
- Select which entity view methods should be available for the MCP tool (none are selected by default).
Available Methods:
get: Retrieve a single entity by its identifier.getAll: Retrieve all entities of a specific type.create: Create a new entity.update: Update an existing entity.delete: Delete an entity by its identifier.
After exposing the MCP tool, you can configure its settings:
Tool Name
By default, the tool name matches the controller method name. You can change it to something more descriptive if desired.
Tool Description
The description helps AI models understand the tool's purpose. By default, it is generated from the entity view route description, but you can customize it to provide more details.
Exposing a server method as an MCP Tool
To expose a server method as an MCP tool in Neos Studio:
- Open the server method's advanced editor (tab: Artificial Intelligence).
- Check the Exposed as MCP tool option.
- Configure the tool name and description as you would for entity views.
- Optionally, define a description for each parameter to provide additional context.
Tool Name
By default, the tool name matches the server method name. You can change it to something more descriptive if needed.
Tool Description
The description helps AI models understand the tool's purpose. By default, it is generated from the server method description or name, but you can customize it for more detail.
Parameter Descriptions
You can add descriptions for each parameter to provide context and help AI models use them effectively. By default, no parameter descriptions are set.
Application context values
When the cluster implements IUserContextPermissions (see Checking the allowed values), every exposed MCP tool, whether it comes from an entity view or a server method, has one extra input parameter per context key. The parameter is named neos- followed by the key, for example neos-CompanyId, like the HTTP header of the REST API.
- The parameter is an array: pass one or several values.
- The parameter is required when the current user is not allowed to omit the key, which means that the collection returned by
GetUserContextAuthorizedValuesfor this user does not contain anullelement. Includenullamong the returned values to make the parameter optional. Never return anullcollection. - The values are checked exactly as they are for the REST API. If a value is not authorized, or a required value is missing, the tool is not executed and the call returns an MCP error.
- The values are then available in the application context for the whole tool execution, for example in IApplicationContext, and are propagated to the other clusters like any other value.
- If the MCP client also sends a
neos-{key}HTTP header, the parameter of the tool takes precedence. The header is used when the parameter is not provided. An empty array means that no value is provided, and hides the header. A connection value that is an empty list ([]) also means that no value is provided: it is accepted only ifnullis among the authorized values. - The application context must expose exactly the values that are checked. A single value that the application context would read differently, such as a text containing a comma, a text like
[]or an empty text, is refused with an MCP error. Provide several values together to keep such texts unchanged. - The elements of the array must be strings, numbers or booleans:
null, objects and nested arrays are refused. - A value that contains an escape sequence such as
%32is refused, whether it comes from the parameter or from the connection (neos-{key}header, cookie or query string): the values given to another cluster are decoded again there, so the checked value would not be the one used. A value of the connection that cannot be read as a list of strings is refused too.
Without an IUserContextPermissions implementation, the tools have no additional parameter.
Function permissions
The permissions of the user on the functions (entity views and server methods) apply to the MCP tools exactly as they apply to the REST API:
- A tool that the user is not allowed to call is not listed.
- Calling it anyway returns an MCP error and the tool is not executed.
- When no user is identified, the tools are available only if the default permission of the cluster allows everything.
- A server method that does not require an authentication has no permission check, on MCP as on the REST API.
The MCP tools are generated, and the permissions of the functions rely on what is generated:
- The functions that protect an entity view or a server method exposed only as an MCP tool (not as an API) are taken into account when the cluster is generated.
- A server method that does not require an authentication has no
AuthorizeResourceattribute on its MCP tool only when it is generated with this version.
The cluster must therefore be regenerated and redeployed after upgrading Neos, before using it in production. The incremental generation does it automatically when it detects a new version of Neos (see Generate). Do not run a cluster generated with a previous version of Neos on the new runtime: its MCP tools for public server methods would be hidden and refused for anonymous calls, and its MCP-only resources would be treated as if no function protected them.
Masked properties
The MCP tools of an entity view return the value of a property with a masking type masked, as the REST API does: the MCP client and its AI model never receive the real value. The getAll tool can still filter and sort on a masked property, on the server.
The create and update tools save values as the REST API does: a value equal to the mask of the stored value keeps the stored value, so that a client sending back what it read does not overwrite the real value, and a value that has the form of a mask is refused with an error (see Saving masked values).
A server method exposed as an MCP tool is masked the same way: when its result holds an entity or an entity view, the properties with a masking type come back masked. The other values it returns, such as a text or a data object property, carry no masking type and come back as its code produces them.
Configuring the MCP Endpoint
By default, the MCP endpoint is available at /api/v1/mcp. You can change this endpoint in your cluster configuration file:
AI:
McpUrlPattern: /api/v1/custom-mcp
In production, you can also set the endpoint using the environment variable:
AI__McpUrlPattern
Viewing and Testing Exposed MCP Tools
You can use MCP Inspector, a third-party tool, to view and test your exposed MCP tools.
To install and run MCP Inspector (requires Node.js v22.7.5 or higher):
npx @modelcontextprotocol/inspector
Configure MCP Inspector with the following settings:
- Transport Type: Streamable HTTP
- URL:
https://localhost/neos/[Your_Cluster_Name]/api/v1/mcp - Connection Type: Direct
Click Connect to view and test the list of exposed MCP tools.
Note
When the cluster has an authentication configured, MCP Inspector must authenticate too (see Connecting from an authenticated MCP client): an unauthenticated call is refused.
Connecting from an authenticated MCP client
When the cluster is protected by an identity provider (for example Azure AD B2C), an external MCP client such as Claude Code has to authenticate before it can use the MCP endpoint. The cluster's MCP endpoint automatically:
- advertises the real authorization server to the client through the standard OAuth discovery documents (
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server), so the client does not have to guess an authorization endpoint on the cluster's own host; - proxies the token endpoint (
/oauth/token) so an MCP client — a public OAuth client using PKCE, with no client secret — can redeem its authorization code even when the identity provider's discovery document does not advertise support for public clients (a known limitation of Azure AD B2C custom policies).
A call to the MCP endpoint without a valid Bearer token (or without the cluster's authentication cookie) is not anonymous: the proxy answers 401 Unauthorized with a WWW-Authenticate header pointing to the discovery documents above, which starts the client's OAuth flow (Claude Code reports a needs_auth status). The call is only forwarded to the cluster once the token is validated, and the user is then identified like for any other API call. The only exceptions are the requests that the cluster already declares anonymous (for example the Neos Studio cluster, or the hosts and routes listed in AnonymousHosts and AnonymousRoutes).
Note
This behavior is implemented by the development proxy (neos run). In a deployment behind the Gateway, the MCP endpoint is protected only if its path is listed in the Gateway's ApiPrefixes configuration. The Neos Helm chart routes /api/v1/** to the cluster backend, and its ApiPrefixes already cover that prefix, so the default endpoint /api/v1/mcp is routed and protected without any configuration. If you change it with AI:McpUrlPattern (see Configuring the MCP Endpoint), keep it under /api/v1/: any other path is neither routed to the cluster backend nor protected by the chart.
Warning
Authenticated MCP access requires the cluster to be hosted on Kestrel and is not compatible with HttpSys (the default web server on Windows). HttpSys enforces its registered URL prefix at the OS level and rejects a .well-known discovery request before it ever reaches the application, because the request does not carry the prefix the cluster is mounted under (for example HttpSys's default http://localhost/neos) — RFC 8414/9728 place .well-known right after the host, ahead of any such prefix. No application-level configuration can work around this. Run the cluster with --webserver Kestrel for any scenario where an MCP client needs to authenticate.
Prerequisite: a dedicated public client application
The MCP client must authenticate with its own App Registration, configured as a public client and distinct from the cluster's own (confidential) web login client. See Configure MCP client authentication for the Azure AD B2C setup, and McpScopes for the cluster-side scope configuration.
Configuring Claude Code
Register the cluster's MCP endpoint as an MCP server, providing the public client's application ID and a fixed callback port matching the loopback redirect URI registered in Azure. Run the cluster with neos run --webserver Kestrel and use its own https://localhost:[PORT] URL — not the shared HttpSys host (see the warning above):
claude mcp add --transport http --scope local --client-id [MCP_CLIENT_ID] --callback-port [CALLBACK_PORT] [SERVER_NAME] https://localhost:[PORT]/api/v1/mcp
Replace :
[MCP_CLIENT_ID]with theApplication (client) IDof the dedicated public client application.[CALLBACK_PORT]with the port of the loopback redirect URI registered for that application (for example5001). Without this flag, Claude Code picks a random port on every attempt, which the identity provider rejects since only the registered port is allowed.[SERVER_NAME]with a name of your choice for this MCP server.[PORT]with the Kestrel port the cluster is listening on (thehttps://one passed toneos run --urls). Under Kestrel the cluster is served from the root of this port, without its name in the URL.
Then start the authentication flow:
claude mcp login [SERVER_NAME]
This opens a browser to complete the sign-in. Once authenticated, claude mcp get [SERVER_NAME] should report a Connected status.
Configuring MCP Inspector with Azure AD B2C
MCP Inspector can also authenticate against a cluster protected by Azure AD B2C. The authentication mechanism is detected automatically: Inspector reads the discovery documents served by the cluster (see Connecting from an authenticated MCP client), so no authorization or token endpoint has to be entered by hand.
Install and start MCP Inspector (requires Node.js v22.7.5 or higher). No prior installation is needed,
npxdownloads and runs it:npx @modelcontextprotocol/inspectorInspector opens in the browser on
http://localhost:6274.Register the two Inspector callback URLs as redirect URIs of the dedicated public client application in Azure (see Prerequisite: a dedicated public client application):
http://localhost:6274/oauth/callbackhttp://localhost:6274/oauth/callback/debug
Allow cross-origin requests from Inspector, then run the cluster with
neos run --webserver Kestrel(see the warning above). With the Direct connection type, the browser calls the cluster fromhttp://localhost:6274while the cluster listens onhttps://localhost:[PORT]. These are different origins, and the cluster rejects cross-origin requests unlessAllowFullCorsis enabled. Set it in the environment the cluster inherits, beforeneos run:$env:AllowFullCors = "true" neos run --webserver KestrelWarning
AllowFullCorsaccepts requests from any origin. Only use it on a local development machine, never in production.Then configure Inspector:
- Transport Type: Streamable HTTP
- URL:
https://localhost:[PORT]/api/v1/mcp - Connection Type: Direct
- Authentication > OAuth 2.0 Flow > Client ID: the
Application (client) IDof the dedicated public client application.
Click Connect. Inspector opens the Azure AD B2C sign-in page, then lists the exposed MCP tools once authenticated.
Note
Inspector validates the authorization server metadata strictly (RFC 8414): the issuer it receives must be identical to the address it used for discovery. The cluster therefore serves its own address as issuer, instead of the Azure AD B2C one.
Connecting to a multitenant cluster
On a multitenant cluster, the tenant identifier is inserted as its own URL segment, immediately before the endpoint path — the same convention already used for the cluster's own web pages (https://mycluster.example.com/[TENANT_IDENTIFIER]/). The MCP endpoint follows the same rule:
claude mcp add --transport http --scope local --client-id [MCP_CLIENT_ID] --callback-port [CALLBACK_PORT] [SERVER_NAME] https://localhost:[PORT]/[TENANT_IDENTIFIER]/api/v1/mcp
Replace [TENANT_IDENTIFIER] with the identifier of the tenant to connect to (as listed by the tenant selection endpoint, GET ./tenant-selection/tenants, or your own custom tenant selection page) — the other placeholders are the same as above. The tools exposed by the MCP endpoint then operate within that tenant's data, exactly as if a user had authenticated and selected that tenant through the regular web application.
Connecting to a cluster authenticated with NeosDevAuth
The steps above are written for Azure AD B2C, but the same proxied discovery and token endpoints work with any authority, including the standard development server (Preset: NeosDevAuth). The setup is actually simpler there, since NeosDevAuth's default client has no client secret and no registered redirect URI:
- skip Prerequisite: a dedicated public client application entirely — the cluster's own
Authentication:ClientId(localhostunless configured otherwise) is already a public client, since NeosDevAuth never issues it a client secret; --callback-portis not required — NeosDevAuth does not validate the redirect URI, so the random callback port Claude Code otherwise picks on every attempt is accepted;Authentication:McpScopesdoes not need to be configured either — the emptyscopes_supportedthis produces only matters for Azure AD B2C, which rejects an authorization request with no scope (error AADB2C90010).
claude mcp add --transport http --scope local --client-id [CLIENT_ID] [SERVER_NAME] https://localhost:[PORT]/api/v1/mcp
Replace [CLIENT_ID] with the cluster's Authentication:ClientId (localhost unless configured otherwise), and [SERVER_NAME] and [PORT] as described above. The cluster still has to run on Kestrel (see the warning above, which applies regardless of the authentication provider). Then start the authentication flow the same way with claude mcp login [SERVER_NAME].