Skip to main content

MCP Server Configuration

1. Introduction

The Universal MCP Server is a thin JSON‑RPC wrapper that exposes Conscia’s orchestrated capabilities (built in the DX Engine) as MCP‑compliant tools. This allows any LLM‑based agent (ChatGPT, Perplexity, Gemini, etc.) or your own web chat client to invoke business capabilities such as product discovery or checkout via a single, standard gateway.

Why a wrapper? 95 % of the heavy lifting—data stitching, transformation, rule evaluation, caching—resides in the DX Engine. The MCP Server simply converts the DX Engine’s JSON payloads into the standard MCP response envelope so that agents can consume them.

2. Prerequisites

  • An active Conscia DX Engine tenant with at least one customer & environment.
  • API URL and token with permission to read the Flows you will expose.
  • Relevant Flows (orchestrations) already authored and tested in DX Engine.

3. MCP Configuration JSON

This section describes how to author an MCP Configuration — the JSON file that defines a single MCP server: how it authenticates callers, which users/tokens may use which tools, the tools it exposes, and how each tool calls the DX Engine.

3.1. How a configuration is located and addressed

Public URL the MCP client connects to:

https://mcp.conscia.ai/{customerCode}/{environmentCode}/{mcpConfigurationCode}

e.g. https://mcp.conscia.ai/ayana-corp/mcp/discovercustomerCode=ayana-corp, environmentCode=mcp, mcpConfigurationCode=discover.

Each configuration is a JSON file identified by three codes:

CodeSource
customerCodethe first path segment
environmentCodethe second path segment
mcpConfigurationCodethe thircd path segment

3.2. Top-level structure

{
"mcpConfigurationCode": "discover", // identity / metadata
"version": "1.0.0",
"name": "Ayana's MCP Server", // shown to the MCP client
"description": "This is Ayana's MCP Server",
"environmentCode": "mcp",

"authentication": { /* §3.3 — how callers prove who they are */ },
"authorization": { /* §3.4 — which caller may use which tools */ },

"dxengine": { /* §3.6 — default DX Engine connection for all tools */ },

"tools": { "mapping": [ /* §5 — the tools exposed */ ] },
"discovery": { /* §3.7 — OAuth protected-resource metadata (oauth only) */ },

"resources": {}, // reserved
"prompts": {} // reserved
}
FieldRequiredNotes
mcpConfigurationCode, version, nameyesmetadata; name/version/description are reported to the client on initialize
description, environmentCodenoinformational
authenticationnodefaults to { "type": "oauth" } (see §3)
authorizationnoper-tool / server gating (see §4)
dxengineyes (if any tool calls DXE)default connection for tool calls (see §6)
tools.mappingyesarray of tool definitions (see §5)
discoveryonly for oauthRFC 9728 metadata (see §7)

3.3. Authentication options (authentication)

authentication.type selects the auth model. If the whole authentication block is omitted, the default is oauth.

"authentication": { "type": "public" | "oauth" | "bearerToken", ... }

3.3.1 public

No authentication. Every caller is anonymous and all tools are available. The server returns a normal 200 to an unauthenticated initialize, so an MCP client connects without any OAuth prompt.

"authentication": { "type": "public" }

A public server still calls the DX Engine using the tokens in its dxengine block, so anyone who knows the URL can invoke its tools. Scope the DX Engine token accordingly.

3.3.2 oauth (default)

Validates a Keycloak-issued JWT (cryptographically verified against the realm's JWKS and issuer). Pairs with the authorization block (§4) to gate access by JWT claims. This is the model used for Claude custom connectors via OAuth.

"authentication": { "type": "oauth" }
  • A missing/invalid token → 401 with a WWW-Authenticate header pointing at the protected-resource metadata (this is what triggers the client's OAuth login).
  • Requires a discovery block (§7) so the client can find the authorization server.

3.3.3 bearerToken

Treats the Authorization: Bearer <token> value as an opaque, static shared secret (it is not verified as a JWT). The token is resolved to a set of permission tags (or full access), which are then matched against authorization.tools (§4) to decide which tools are exposed.

The block may contain a tokenPermissions array and/or a dxengine block:

"authentication": {
"type": "bearerToken",

"tokenPermissions": [ // optional; checked first
{ "token": "abc-123-xyz", "permissions": ["tag-one"] },
{ "token": "def-456-uvw", "permissions": ["tag-two", "tag-one"] },
{ "token": "ghi-789-mno", "fullAccess": true }
],

"dxengine": { // optional; fallback lookup
"templateCode": "get-permissions",
"responseTransform": "`response.components['get-permissions'].response`" // optional
}
}

Resolution order for the presented token:

  1. tokenPermissions (precedence, terminal). Find the entry whose token exactly equals the presented token. If found, that entry decides the outcome — the DX Engine fallback is not consulted:
    • "fullAccess": true → all tools.
    • "permissions": [ ... ] (non-empty) → those permission tags.
    • neither → denied (matched, but grants nothing).
  2. dxengine fallback (only when no tokenPermissions entry matched). Calls the DX Engine with templateCode and a context of exactly { "token": "<token>" }. The response is unwrapped with responseTransform (defaults to `response.components['<templateCode>'].response` — the same wrapping tool calls use) and must be one of:
    { "fullAccess": true }
    or
    { "permissions": ["tag-one", "tag-three"] }
  3. Denied (HTTP 403, with a descriptive server log) when: no token (→ 401), no tokenPermissions match and no dxengine block, the DX Engine response is non-conforming, or the resolved access is empty (fullAccess:false / permissions:[]).

At least one of tokenPermissions / dxengine should be present. Raw tokens are never logged — only a short SHA-256 hash.


3.4. Authorization options (authorization)

The authorization block maps a caller's values to the tools they may use. The source of those values depends on the authentication type:

auth typevalues come fromauthorization.claim used?
oautha JWT claim (authorization.claim)yes
bearerTokenthe token's resolved permissions tagsno (ignored)
publicn/a — authorization is not appliedno
"authorization": {
"claim": "realm_access.roles", // oauth only: dot-path into the JWT payload
"allowedValues": ["mcp-access"], // any-of → access to the server + all tools w/o an override
"tools": { // optional per-tool any-of overrides
"listCollections": ["tag-one"],
"getCollectionByCode": ["tag-two"]
}
}

Fields

  • claim (oauth only) — dot-path into the JWT payload, e.g. realm_access.roles (Keycloak), groups, or scope. The resolved value is normalized to a set of strings (arrays → items; a string → split on whitespace, which also covers space-delimited scope). This makes the model IdP-agnostic — point claim at whatever your IdP emits.
  • allowedValues — the default set of values. A caller holding any of them gets server access and access to every tool that does not have its own override.
  • tools — per-tool overrides. tools[<toolName>] is the any-of list of values required for that specific tool (it replaces allowedValues for that tool).

Semantics (all matching is any-of / OR, case-sensitive)

  • Per-tool requirement = tools[name] if present, else allowedValues, else none (open).
  • Tool access = caller's value-set intersects the tool's requirement (or the requirement is none).
  • Server access = the caller can access at least one tool; otherwise the whole connection is rejected with 403.
  • fullAccess (from a bearerToken resolution) bypasses all per-tool checks → every tool.
  • If the authorization block is omitted, no per-tool gating is applied — every authenticated caller sees every tool.

Example: who sees what

With the tools overrides above:

Caller valueslistCollectionsgetCollectionByCode
["tag-one"]
["tag-two"]
["tag-one","tag-two"]
fullAccess
none / unrelatedserver access denied (403)

3.5. Tool definitions (tools.mapping[])

Each entry defines one tool exposed to the MCP client.

{
"name": "getCollectionByCode", // tool id the client/LLM calls
"description": "Get a collection's details by code. ...", // tell the LLM when/how to use it
"schema": { // JSON Schema of the tool's arguments
"type": "object",
"properties": {
"collectionCode": { "type": "string", "description": "The code of the collection to get." }
},
"required": ["collectionCode"]
},
"dxengine": { // how this tool calls the DX Engine (see §6)
"templateCode": "get-collection-by-code",
"responseTransform": "`response.components['get-collection-by-code'].response`"
}
}
FieldRequiredNotes
nameyesunique tool identifier
descriptionyeswritten for the LLM — describe purpose, inputs, and output thoroughly
schemayesa JSON Schema object for the arguments; converted to a Zod schema at runtime. Use title/description on each property and a required array
dxengineyes (to call DXE)per-tool DX Engine settings; missing fields fall back to the top-level dxengine block (§6)

The tool name is matched against authorization.tools for per-tool gating (§4).


3.6. DX Engine calls (dxengine)

There are two dxengine blocks:

  • Top-level dxengine — the default connection (URL + token + customer/environment) used by all tools and by the bearerToken permissions lookup.
  • Per-tool dxengine (inside a tools.mapping[] entry) — the template to run, plus optional overrides of the connection fields.
"dxengine": {
"url": "https://query.conscia.io/api",
"token": "eyJhbGci...", // DX Engine auth token (Bearer)
"customerCode": "ayana-corp",
"environmentCode": "mcp"
}

What a tool call sends

When a tool is invoked, the service issues:

POST {url}/experience/template/_query
Authorization: Bearer {token}
x-customer-code: {customerCode}
x-environment-code: {environmentCode}

{ "templateCode": "<tool.dxengine.templateCode>", "context": <the tool's arguments> }

Field resolution (per-tool value wins, then top-level, then the request's codes):

Sent valueResolution order
url, tokentool.dxengine.*mcpConfig.dxengine.*
x-customer-codetool.dxengine.customerCodemcpConfig.tools.customerCodemcpConfig.dxengine.customerCode → request customerCode
x-environment-codetool.dxengine.environmentCodemcpConfig.tools.environmentCodemcpConfig.dxengine.environmentCode → request environmentCode

Per-tool dxengine fields

FieldNotes
templateCoderequired — the DX Engine experience/template to run
url, token, customerCode, environmentCodeoptional overrides of the top-level connection
responseTransformoptional expression to shape the response (see below)
contextTransformpresent in the schema but not currently applied — the tool's raw arguments are sent as context as-is

responseTransform and the expression language

responseTransform is evaluated against the DX Engine response, with the raw parsed body bound to a variable named response. DX Engine wraps template output under components['<templateCode>'].response, so the common form is:

"responseTransform": "`response.components['get-collection-by-code'].response`"

Expression syntax (handled by src/utils/evaluate-object.js):

  • `…` (backticks) → a JavaScript/ES6 expression, e.g. `response.components['x'].response`.
  • #…# → a JSONata expression.

If responseTransform is omitted, the full HTTP response object is returned. The tool result is returned to the client as JSON text.


3.7. Discovery block (discovery) — oauth only

For oauth configs, this provides the RFC 9728 protected-resource metadata the MCP client fetches after a 401 to discover the authorization server.

"discovery": {
"resource": "http://localhost:5030", // overridden at runtime with the live request URL
"authorization_servers": ["https://keycloak.conscia.io/realms/conscia-dev"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["openid", "profile", "email"], // advisory only — not enforced
"resource_name": "Test MCP Server"
}
FieldNotes
resourcethe canonical resource URL; automatically overridden at runtime to the live https://{host}/{env}/{config}
authorization_serversissuer URL(s) of the IdP (Keycloak realm)
bearer_methods_supportedhow the token is sent (header)
scopes_supportedadvisory — tells the client which scopes it may request; enforcement is via authorization (§4), not this list
resource_namehuman label

public and bearerToken configs do not need a discovery block (they never emit the OAuth 401).


3.8. Complete examples

3.8.1 OAuth + role-gated tools

{
"mcpConfigurationCode": "discover",
"version": "1.0.0",
"name": "DX Graph MCP Server",
"environmentCode": "mcp",
"authentication": { "type": "oauth" },
"authorization": {
"claim": "realm_access.roles",
"allowedValues": ["mcp-access"],
"tools": { "getCollectionByCode": ["mcp-admin"] }
},
"discovery": {
"authorization_servers": ["https://keycloak.conscia.ai/realms/conscia-dev"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["openid", "profile", "email"],
"resource_name": "Test MCP Server"
},
"dxengine": {
"url": "https://query.conscia.io/api",
"token": "eyJ...",
"customerCode": "ayana-corp",
"environmentCode": "mcp"
},
"tools": {
"mapping": [
{
"name": "listCollections",
"description": "List all collections",
"schema": { "type": "object", "properties": { "customerCode": { "type": "string" } }, "required": ["customerCode"] },
"dxengine": { "templateCode": "list-collections", "responseTransform": "`response.components['list-collections'].response`" }
},
{
"name": "getCollectionByCode",
"description": "Get a collection's details by code.",
"schema": { "type": "object", "properties": { "collectionCode": { "type": "string" } }, "required": ["collectionCode"] },
"dxengine": { "templateCode": "get-collection-by-code", "responseTransform": "`response.components['get-collection-by-code'].response`" }
}
]
}
}

A user needs the mcp-access realm role to connect and use listCollections, and the mcp-admin role to additionally use getCollectionByCode.

8.2 Static bearer tokens

{
"mcpConfigurationCode": "bearerdemo",
"version": "1.0.0",
"name": "DX Graph MCP Server (Bearer Token)",
"environmentCode": "mcp",
"authentication": {
"type": "bearerToken",
"tokenPermissions": [
{ "token": "abc-123-xyz", "permissions": ["tag-one"] },
{ "token": "ghi-789-mno", "fullAccess": true }
],
"dxengine": { "templateCode": "get-permissions" }
},
"authorization": {
"tools": { "listCollections": ["tag-one"], "getCollectionByCode": ["tag-two"] }
},
"dxengine": { "url": "https://query.conscia.io/api", "token": "eyJ...", "customerCode": "ayana-corp", "environmentCode": "mcp" },
"tools": { "mapping": [ /* listCollections, getCollectionByCode */ ] }
}

abc-123-xyz sees only listCollections; ghi-789-mno (fullAccess) sees both; any other token is resolved via the get-permissions DX Engine template, or denied.

8.3 Public (no auth)

{
"mcpConfigurationCode": "publicdemo",
"version": "1.0.0",
"name": "DX Graph MCP Server (Public)",
"environmentCode": "mcp",
"authentication": { "type": "public" },
"dxengine": { "url": "https://query.conscia.io/api", "token": "eyJ...", "customerCode": "ayana-corp", "environmentCode": "mcp" },
"tools": { "mapping": [ /* tools available to everyone */ ] }
}

4. Quick reference

{
"mcpConfigurationCode": "string", // required
"version": "string", // required
"name": "string", // required
"description": "string",
"environmentCode": "string",

"authentication": {
"type": "public" | "oauth" | "bearerToken", // default "oauth"
// bearerToken only:
"tokenPermissions": [ { "token": "string", "permissions": ["string"] | "fullAccess": true } ],
"dxengine": { "templateCode": "string", "responseTransform": "string?" }
},

"authorization": {
"claim": "string", // oauth only (dot-path into JWT)
"allowedValues": ["string"],
"tools": { "<toolName>": ["string"] }
},

"dxengine": { "url": "string", "token": "string", "customerCode": "string", "environmentCode": "string" },

"tools": {
"mapping": [
{
"name": "string",
"description": "string",
"schema": { /* JSON Schema */ },
"dxengine": {
"templateCode": "string",
"url": "string?", "token": "string?", "customerCode": "string?", "environmentCode": "string?",
"responseTransform": "string?"
}
}
]
},

"discovery": { // oauth only
"authorization_servers": ["string"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["string"],
"resource_name": "string"
}
}

5. API Reference (DX Engine & MCP Server)

Below is a concise reference for the endpoints used by the MCP Server when talking to Conscia’s DX Engine. Replace the variables in {{CAPS}} with real values at runtime.

#Method & PathPurpose
1POST /mcpCreate (or upsert) an MCP configuration.
2PATCH /mcp/{mcpConfigurationCode}Update an existing MCP configuration.
3GET /mcp/{mcpConfigurationCode}Retrieve a single MCP configuration.
4GET /mcpList all MCP configurations for a customer/environment.
5DELETE /mcp/{mcpConfigurationCode}Delete an MCP configuration.

5.1 Common Headers

Content-Type: application/json
Authorization: Bearer {{DX_ENGINE_SYSTEM_TOKEN}}
X-Customer-Code: {{customerCode}}
X-Environment-Code: {{environmentCode}}

Authorization must carry a System‑level PAT (scope: System). PATs scoped to Query will not be able to create or update MCP configurations.

9.2 Create or Upsert an MCP Configuration

POST {{DX_ENGINE_URL}}/mcp

# body snippet
{
"mcpConfiguration": {
"mcpConfigurationCode": "bestbuy",
"version": "1.0.0",
"name": "My First MCP Configuration",
...
"tools": { "mapping": [ /* see §5 */ ] },
"resources": {},
"prompts": {}
}
}

9.3 Update an MCP Configuration (Partial)

PATCH {{DX_ENGINE_URL}}/mcp/bestbuy

# body identical to POST

9.4 Retrieve an MCP Configuration

GET {{DX_ENGINE_URL}}/mcp/bestbuy

Response is the stored JSON object.

9.5 List All MCP Configurations

GET {{DX_ENGINE_URL}}/mcp

9.6 Delete an MCP Configuration

DELETE {{DX_ENGINE_URL}}/mcp/bestbuy

  • DX Engine Docs → Components, Flows, Templates
  • Recipes → Multi‑Brand Conversational Commerce, Checkout Orchestration