Access to AO Platform Ontology from 3rd Party Products (Preview)

MCP Server - Configuration and Deployment

Intended audience: Developers administrators

AO Platform: 5.0


Architecture

What This Service Does

Capability

Detail

MCP tool host

13 tools and 5 prompts via JSON-RPC 2.0 over Streamable HTTP at /mcp

Auth forwarding

Passes Bearer token or API key from the inbound MCP request to every AO Platform call

OAuth metadata

Serves /.well-known/oauth-protected-resource (RFC 9728) so clients auto-discover the auth server

Platform 401 propagation

Converts AO Platform 401 responses into a proper HTTP 401 + WWW-Authenticate to the MCP client, triggering re-authentication

Retry & timeout

Retries platform 5xx with exponential backoff; per-tool configurable timeout


Component Diagram

image-20260623-033247.png


Request Flow

image-20260623-033438.png


MCP Tools & Prompts

All tools are invoked through the MCP protocol at /mcp. Parameters are passed as MCP arguments; return values are formatted text.


Semantic SQL Tools

Tool

Platform Endpoint

Required Params

Optional Params

execute-semantic-sql

POST /api/easyAnswers/getDataBySemanticSQL

ontologyUid, semanticSql

limit (default 500), isPassThrough (default true)

nlq-to-semantic-sql

POST /api/agora/easyAnswers/agents/getSemanticSql

question, ontologyUid, checkForContextChange, conversationId, workflowId

queryMode, useQueryCache, useDataCache, solutionId, feedback, ambiguityClarification, ambiguityClarificationMessage, ambiguityFeedbackMso, ambiguityFeedbackMsoProperty, generateDataSummary, replaceTokensJson, previousQuestion

validate-semantic-sql

POST /api/sor/semanticSchema/validateSemanticSql

ontologyUid, semanticSql

—

Recommended two-step workflow:

  1. nlq-to-semantic-sql — converts the question to SQL; returns semanticSQL, ambiguity info, and session IDs (conversationId, workflowId)

  2. Review / optionally validate-semantic-sql

  3. execute-semantic-sql — only call this when semanticSQL is present in step 1's response

If step 1 returns ambiguity only (no SQL), do not call execute-semantic-sql — disambiguate with the user or recall NLQ with feedback or structured ambiguity params.

Reuse conversationId and workflowId on follow-up turns. When contextChanged is true, use the IDs from that response on the next call.


NLQ Tools

Tool

Platform Endpoint

Required Params

Optional Params

nlq-get-data

POST /api/agora/easyAnswers/agents/nlqToData

question, ontologyUid, conversationId, workflowId, checkForContextChange, useQueryCache, useDataCache

solutionUid, queryMode, feedback, generateDataSummary, page, pageSize, ambiguityClarification, ambiguityClarificationMessage, ambiguityFeedbackMso, ambiguityFeedbackMsoProperty, replaceTokensJson, previousQuestion

Use nlq-get-data when the client needs a one-shot answer, and SQL visibility is not required. Use nlq-to-semantic-sql + execute-semantic-sql when the client needs to inspect or refine the SQL before running it.


Schema Discovery Tools

Tool

Platform Endpoint

Required Params

Optional Params

list-ontologies

GET /api/ontology/all

—

—

list-msos

GET /api/sor/semanticSchema/msos

ontologyUid

excludeLlmPriorityNever (default true)

describe-mso

GET /api/sor/semanticSchema/mso

ontologyUid, msoUid

—

search-schema

GET /api/sor/semanticSchema/search

ontologyUid, keyword

excludeLlmPriorityNever (default true)

list-relationships

GET /api/sor/semanticSchema/joinRelationships

ontologyUid

msoName, excludeLlmPriorityNever (default true)

get-mso-sample-data

GET /api/sor/semanticSchema/msoSampleRows

ontologyUid, msoUid

limit (platform default 5)

get-mso-column-details

GET /api/sor/semanticSchema/msoColumns + filter

ontologyUid, msoUid, columnName

—

get-mso-statistics

GET /api/sor/semanticSchema/msoStatistics

ontologyUid, msoUid

columnName

export-ontology-schema

GET /api/schemaExport

ontologyName (not UID)

—

Note: All SOR tools use ontologyUid. Only export-ontology-schema uses ontologyName. export-ontology-schema returns the full schema JSON and can be tens of thousands of tokens — use incremental SOR tools first.


Typical Flows

Goal

Tool Sequence

Unknown ontology, answer a question

list-ontologies → list-msos / search-schema / describe-mso → nlq-to-semantic-sql → validate-semantic-sql → execute-semantic-sql

Known ontology, run raw SQL

execute-semantic-sql directly

One-shot answer, no SQL review needed

nlq-get-data

Full schema dump (last resort)

export-ontology-schema with ontologyName


MCP Prompts

Prompt

Purpose

schema-on-read-intro

Discovery workflow: list ontologies → SOR tools → validate → execute; when to use export

schema-on-read-params

Parameter reference for SOR tools (ontologyUid vs ontologyName, msoUid vs msoName, etc.)

semantic-sql-intro

NLQ-to-SQL workflow, ambiguity handling, session ID reuse, when to call execute

semantic-sql-params

Full parameter reference for nlq-to-semantic-sql and execute-semantic-sql

when-to-use-natural-language-vs-semantic-sql

Decision guide: NLQ one-shot vs two-step SQL vs raw SQL vs schema tools


Query Workflow

The skill follows a five-step workflow for each data question:

Step 1 — Identify the Ontology. If only one ontology exists, use it. If multiple exist, call `list-ontologies` and let the user choose.

Step 2 — Generate Semantic SQL. Call `nlq-to-semantic-sql` with the user's question. The tool supports three query modes: Controlled (strict matching), Balanced (default — moderate flexibility), and Freeform (liberal interpretation). If ambiguity is returned instead of SQL, the skill presents options to the user and re-calls with their selection. If the tool errors out, the skill explains the failure and offers to attempt manual SQL construction — only with user confirmation.

Step 3 — Execute the Query. Run the generated SQL via `execute-semantic-sql`. On failure or empty results, the skill explains what happened and asks the user how to proceed.

Step 4 — Schema Lookup (only if needed). If SQL or results are unclear, use incremental schema tools to verify column names, sample values, or relationships.

Step 5 — Offer Visualization. After presenting results as a plain English summary, the skill offers to generate a Chart.js visualization. Chart type is selected based on data shape (e.g., pie for small category counts, line for time series, bar for comparisons).


Ambiguity Resolution

When `nlq-to-semantic-sql` detects ambiguity, the skill handles two types:

  • MSO ambiguity: The question matches multiple entities (e.g., "financial entities" could mean Bills, Payments, or Contract Account). The skill presents options and recalls with the user's choice.

  • MSO_PROPERTY ambiguity: The question matches multiple properties within an entity (e.g., "$50" could refer to Final Balance, Balance Forward, or Original Balance). Same resolution pattern.

The skill loops up to 3 times. If ambiguity persists, it suggests rephrasing the question or switching query modes.


Error Handling

On any failure, the skill follows a strict protocol: state what failed, explain in plain English, offer 1–2 specific next steps, and wait for user confirmation. It never silently retries or modifies a query without the user's go-ahead.


API Reference

MCP Endpoint

Path

Method

Auth Required

Description

/mcp

POST

Yes

MCP JSON-RPC 2.0 tool calls (Streamable HTTP)

/mcp

GET

Yes

MCP server-sent events stream

Authentication: Authorization: Bearer <token> or X-Platform-Api-Key / X-API-Key header. Both missing → 401 with WWW-Authenticate pointing to /.well-known/oauth-protected-resource/mcp.

Disable auth for local dev: MCP_REQUIRE_AUTH=false.

Example tool call:

JSON
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "nlq-to-semantic-sql",
    "arguments": {
      "question": "How many invoices are overdue?",
      "ontologyUid": "ont-abc123",
      "checkForContextChange": true,
      "conversationId": "",
      "workflowId": ""
    }
  },
  "id": 1
}

Example success response:

{
  "jsonrpc": "2.0",
  "result": {
    "content": [{ "type": "text", "text": "Semantic SQL: SELECT COUNT(*) FROM Invoices WHERE dueDate < NOW()\nconversationId: conv-xyz\nworkflowId: wf-abc" }]
  },
  "id": 1
}


OAuth Protected Resource Metadata

Served per RFC 9728. Both paths return identical JSON.

Path

Method

Auth

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

GET

None

GET /.well-known/oauth-protected-resource/mcp

GET

None

Response 200 OK:

JSON
{
  "resource": "https://your-mcp-host.example.com",
  "authorization_servers": ["https://your-auth-host.example.com"]
}

MCP clients fetch this on receiving a 401 to discover the OAuth authorization server (ao-mcp-security-service). Configured via MCP_RESOURCE_URL and AUTH_SERVER_URL.


Health & Info

Path

Description

GET /actuator/health

Liveness probe; includes platform sub-indicator (AO Platform reachability)

GET /actuator/info

Application metadata

Health response:

JSON
{
  "status": "UP",
  "components": {
    "platform": { "status": "UP" }
  }
}


Authentication

Bearer Token (SSO / OIDC)

Inbound (MCP client → ao-mcp-server):

  • Client sends Authorization: Bearer <JWT> on each /mcp request.

  • McpBearerAuthFilter enforces presence; returns 401 if neither Bearer nor API key is present.

  • BearerTokenForwardingFilter extracts the token and stores it in BearerTokenContext (ThreadLocal).

Outbound (ao-mcp-server → AO Platform):

  • WebClient outbound filter resolves credentials in this order:

    1. BearerTokenContext — set from the inbound MCP request

    2. Authorization header from the current servlet request (fallback)

    3. Static PLATFORM_BEARER_TOKEN env var (last resort — automated testing only)

AO Platform validates the JWT: signature (JWKS), issuer (iss), audience (aud), expiry. ao-mcp-server does not validate tokens.


API Key

Inbound:

  • Client sends X-Platform-Api-Key or X-API-Key header.

  • PlatformApiKeyForwardingFilter extracts and stores it in PlatformApiKeyContext (ThreadLocal).

  • McpBearerAuthFilter accepts either Bearer or API key — either satisfies the auth requirement.

Outbound:

  • WebClient sends the key as x-api-key to AO Platform, resolved in order:

    1. PlatformApiKeyContext — set from the inbound MCP request (preferred)

    2. Static PLATFORM_API_KEY env var (fallback when no inbound key)


Filter Chain

Filters execute in this order on every /mcp request:

Order

Filter

Responsibility

+5

McpAcceptHeaderFilter

Injects Accept: application/json, text/event-stream if absent (Spring AI requirement)

+6

JsonRpcErrorEnvelopeFilter

Guarantees well-formed JSON-RPC 2.0 error responses; handles session-not-found (404)

+7

PlatformUnauthorizedPropagationFilter

Detects platform 401 marker; rewrites JSON-RPC 200 error to HTTP 401 + WWW-Authenticate

+8

McpBearerAuthFilter

Enforces credential presence; returns 401 + resource metadata when missing

+9

BearerTokenForwardingFilter

Extracts Bearer token → ThreadLocal; clears in finally

+10

PlatformApiKeyForwardingFilter

Extracts API key → ThreadLocal; clears in finally

ThreadLocals are always cleared in finally blocks — no credential leakage across requests.


Platform 401 Propagation

When AO Platform rejects a token, the server converts the internal JSON-RPC error into a real HTTP 401:

image-20260623-034618.png


Configuration Reference

Environment Variables

Variable

Required

Default

Description

PLATFORM_SERVER_BASE_URL

Yes

http://localhost:8090

Base URL of the AO Platform API. No trailing slash.

MCP_RESOURCE_URL

Yes (deployed)

http://localhost:8080

Public HTTPS URL of this server. Appears in /.well-known/oauth-protected-resource and WWW-Authenticate headers. No trailing slash, no /mcp suffix.

AUTH_SERVER_URL

Yes (deployed)

http://localhost:9000

Public URL of ao-mcp-security-service. Appears in authorization_servers in the protected resource metadata.

MCP_REQUIRE_AUTH

No

true

Set to false to disable auth enforcement on /mcp. Local dev only — never disable in production.

PLATFORM_BEARER_TOKEN

No

(none)

Static fallback Bearer token for outbound platform calls. Leave blank in production — use per-request credentials.

PLATFORM_API_KEY

No

(none)

Static fallback API key for outbound platform calls. Leave blank in production.

PLATFORM_TRACE_LOG_SENSITIVE_HEADERS

No

false

When true, logs raw Authorization and x-api-key headers. Never enable in production.

application.yml Defaults

server:
  port: 8080

spring:
  application:
    name: ao-mcp-server
  ai:
    mcp:
      server:
        protocol: STREAMABLE   # STATELESS for stateless/cloud-native deployments

app:
  mcp:
    require-auth: ${MCP_REQUIRE_AUTH:true}
    resource-url: ${MCP_RESOURCE_URL:http://localhost:8080}
    auth-server-url: ${AUTH_SERVER_URL:http://localhost:9000}

  platform:
    semantic-sql-tool-timeout: 250s
    server:
      base-url: ${PLATFORM_SERVER_BASE_URL:http://localhost:8090}
      bearer-token: ${PLATFORM_BEARER_TOKEN:}
      api-key: ${PLATFORM_API_KEY:}
      connection-timeout-millis: 15000
      read-timeout-millis: 480000          # 8 minutes
      max-response-buffer-size-bytes: 10485760  # 10 MB
      trace-log-sensitive-headers: ${PLATFORM_TRACE_LOG_SENSITIVE_HEADERS:false}
      retry:
        max-attempts: 3
        first-backoff: 500                 # ms
        max-backoff: 2000                  # ms

  cors:
    allowed-origins: ["*"]
    allowed-methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
    allowed-headers: ["*"]
    max-age: 3600
    allow-credentials: false

management:
  endpoints.web.exposure.include: health,info
  endpoint.health.show-details: always
  tracing.sampling.probability: 1.0


Timeouts & Retry Tuning

Setting

Default

Notes

connection-timeout-millis

15000 (15 s)

TCP connect to AO Platform. Raise on slow networks.

read-timeout-millis

480000 (8 min)

Platform response read. NLQ and Semantic SQL on large datasets can be slow — do not lower without testing.

semantic-sql-tool-timeout

250s

Per-MCP-tool timeout. Should be ≤ read-timeout-millis. Increase if platform NLQ calls regularly exceed 250 s.

retry.max-attempts

3

Total attempts including the first. Set to 1 to disable retry.

retry.first-backoff

500 ms

Backoff before the second attempt.

retry.max-backoff

2000 ms

Cap for exponential backoff.

max-response-buffer-size-bytes

10485760 (10 MB)

In-memory buffer for platform responses. Increase for export-ontology-schema on large ontologies.


Deployment

Docker

The Dockerfile uses a multi-stage build:

  1. Build stage (Amazon Corretto 21 JDK + Amazon Linux 2023): Runs gradle bootJar. No Gradle in the runtime image.

  2. Runtime stage (Amazon Corretto 21 headless JRE): Copies fat JAR, runs as non-root user app. Exposes port 8080.

Build the image:

docker build -t ao-mcp-server:latest .

Run the container:

docker run -p 8080:8080 \
  -e PLATFORM_SERVER_BASE_URL=https://platform.apporchid.com \
  -e MCP_RESOURCE_URL=https://your-mcp-host.apporchid.com \
  -e AUTH_SERVER_URL=https://your-auth-host.apporchid.com \
  ao-mcp-server:latest

Pass JAVA_OPTS for JVM tuning:

-e JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC"

App arguments can be appended after the image name:

docker run ... ao-mcp-server:latest --server.port=9090

On Linux hosts, replace host.docker.internal with the host IP or an externally reachable platform URL.


Environment Checklist

  • PLATFORM_SERVER_BASE_URL points to the correct AO Platform environment

  • MCP_RESOURCE_URL is the exact public HTTPS URL of this server — no trailing slash, no /mcp suffix

  • AUTH_SERVER_URL points to ao-mcp-security-service

  • MCP_REQUIRE_AUTH=true (default) — never disable in production

  • PLATFORM_BEARER_TOKEN and PLATFORM_API_KEY are blank (rely on per-request credentials from MCP clients)

  • PLATFORM_TRACE_LOG_SENSITIVE_HEADERS=false (default)

  • Anthropic egress IP range 160.79.104.0/21 is allowlisted if Claude connector is used

  • GET /actuator/health returns {"status":"UP"} with platform: UP before routing traffic


Local Development

Prerequisites

  • Java 21 — set JAVA_HOME to the JDK 21 home (e.g. C:\Program Files\Java\jdk-21.0.2 on Windows)

  • Gradle wrapper included — no separate Gradle install needed (./gradlew / gradlew.bat)

Build & run

# Build
./gradlew build           # macOS / Linux
gradlew.bat build         # Windows

# Run
./gradlew bootRun

Minimal local config (no auth, local platform)

export MCP_REQUIRE_AUTH=false
export PLATFORM_SERVER_BASE_URL=http://localhost:8090
./gradlew bootRun

Or edit src/main/resources/application.yml:

Ruby
app:
  mcp:
    require-auth: false
  platform:
    server:
      base-url: http://localhost:8090

Debug logging

export LOGGING_LEVEL_COM_APPORCHID_FILTER_MCPBEARERAUTH=DEBUG        # auth enforcement
export LOGGING_LEVEL_COM_APPORCHID_CLIENT_PLATFORMSERVERCLIENT=TRACE  # platform HTTP payloads
export LOGGING_LEVEL_COM_APPORCHID_CLIENT=DEBUG                       # outbound credential resolution


Connecting MCP Clients

The MCP endpoint is always at /mcp. Always use the full URL including the path.

Always pass --timeout 600 on the bridge — Semantic SQL and NLQ calls can take longer than the bridge's default POST timeout.


Claude Desktop — API Key (Via ao-mcp-bridge, Zero-Install)

%APPDATA%\Claude\claude_desktop_config.json (Windows) · ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) · ~/.config/Claude/claude_desktop_config.json (Linux):

JSON
{
  "mcpServers": {
    "ao-mcp-server": {
      "command": "npx",
      "args": [
        "-y", "ao-mcp-bridge@latest",
        "https://your-mcp-host.apporchid.com/mcp",
        "--header", "X-Platform-Api-Key: ak_YOUR_KEY_HERE",
        "--timeout", "600"
      ]
    }
  }
}


Claude Desktop — OAuth / SSO (Via ao-mcp-bridge)

The bridge auto-discovers the auth server via /.well-known/oauth-protected-resource, runs PKCE, and forwards the resulting token:

JSON
{
  "mcpServers": {
    "ao-mcp-server": {
      "command": "npx",
      "args": [
        "-y", "ao-mcp-bridge@latest",
        "https://your-mcp-host.apporchid.com/mcp",
        "--oauth",
        "--timeout", "600"
      ]
    }
  }
}

For local repo checkout, replace npx -y ao-mcp-bridge@latest with node <bridge-path>/mcp-bridge.mjs. See scripts/README.md for full options.


Claude Custom Connector (Cloud-to-Cloud)

Set the connector URL to https://your-mcp-host.apporchid.com/mcp and configure OAuth credentials via the ao-mcp-security-service admin API.


Claude MCP Server Skill

The ao-mcp-query skill is bundled with the AO MCP Server plugin for Claude. For Claude Code, the skill files are located under claude/skills/ao-mcp-query/ in the project repository. The skill is activated automatically when the ao-mcp-server tools are available and the user asks a data question.

ao-mcp-query-2.0.2.skill

ao-mcp-query-2.0.3.skill


Cursor (Native HTTP MCP)

Project-level (.cursor/mcp.json) or global (~/.cursor/mcp.json):

JSON
{
  "mcpServers": {
    "ao-mcp-server": {
      "url": "https://your-mcp-host.apporchid.com/mcp",
      "headers": {
        "X-Platform-Api-Key": "ak_YOUR_KEY_HERE"
      }
    }
  }
}

For local dev, use http://127.0.0.1:8080/mcp. Restart Cursor after changing config.


Observability

Health

GET /actuator/health

Includes a platform sub-indicator that probes AO Platform reachability. Full details shown by default (show-details: always).


Tracing

  • Micrometer Tracing enabled by default — traceId and spanId in every log line.

  • 100% sampling (management.tracing.sampling.probability: 1.0).

  • No OTLP exporter configured out of the box. Add management.otlp.tracing.endpoint or a Zipkin exporter to ship traces externally.


Key Log Events

Event

Level

What To Look For

Startup

INFO

PLATFORM_SERVER_BASE_URL confirmed

Auth enforcement

WARN

McpBearerAuthFilter — missing credential, 401 returned

Outbound credential

DEBUG

Which credential (Bearer / API key) is used for platform call

Platform HTTP payloads

TRACE

PlatformServerClient — full request/response (headers sanitized by default)

Platform 401

WARN

Token rejected by platform; client re-auth triggered

Platform 5xx + retry

WARN

Retry attempt N/3 for platform endpoint

Tool timeout

WARN

semantic-sql-tool-timeout exceeded


Testing & Code Quality

# Run all tests
./gradlew test

# Run tests + generate JaCoCo coverage report
./gradlew test jacocoTestReport
# Coverage report: build/reports/jacoco/test/jacocoTestReport.html


SonarQube (Jenkins CI)

Variable

Required

Description

SONAR_HOST_URL

Yes

SonarQube server URL

SONAR_TOKEN

Yes

User token with Execute Analysis permission

SONAR_PROJECT_KEY

No

Project key (default: ao-mcp-server)

SONAR_ORGANIZATION

No

Organisation key (SonarCloud / org-scoped SonarQube)

withCredentials([string(credentialsId: 'sonar-token', variable: 'SONAR_TOKEN')]) {
    env.SONAR_HOST_URL = 'https://sonarqube.mycompany.com'
    sh './gradlew test jacocoTestReport sonar'
}


Troubleshooting

Symptom

Most Likely Cause

Resolution

Immediate 401 from /mcp on first call

No Bearer token or API key sent

Add Authorization: Bearer <token> or X-Platform-Api-Key: <key> header; or set MCP_REQUIRE_AUTH=false for local dev

401 from /mcp after a successful tool call

AO Platform rejected the token (expired / wrong audience)

Re-authenticate via PKCE; confirm platform aud and issuer configuration

Tool call times out

Platform slow or semantic-sql-tool-timeout too short for this query

Increase semantic-sql-tool-timeout and read-timeout-millis; check AO Platform health

/.well-known/oauth-protected-resource returns wrong URLs

MCP_RESOURCE_URL or AUTH_SERVER_URL not set

Verify both env vars are public HTTPS URLs without trailing slashes

platform health indicator shows DOWN

PLATFORM_SERVER_BASE_URL unreachable from the container

Check URL, network connectivity, and platform status; verify no firewall is blocking

Tools not discovered by client

Wrong endpoint URL or missing /mcp path suffix

Ensure client URL ends with /mcp; check client logs for JSON-RPC errors

export-ontology-schema fails or truncates

Schema exceeds max-response-buffer-size-bytes (default 10 MB)

Increase app.platform.server.max-response-buffer-size-bytes

Claude connector cannot reach server

Anthropic egress IPs not allowlisted

Allowlist 160.79.104.0/21 on the server's firewall / security group

unknown tool error from client

Client config references old tool names

Rename: execute-mso-query → execute-semantic-sql, msoQuery param → semanticSql; see breaking changes note in MCP Tools & Prompts


Tool & Prompt Name Changes (Breaking)

If existing client configs or automation reference old names, update them:

Old Name

New Name

Tool: execute-mso-query

execute-semantic-sql

Param: msoQuery

semanticSql

Prompt: mso-query-intro

semantic-sql-intro

Prompt: mso-query-params

semantic-sql-params

Prompt: when-to-use-natural-language-vs-mso-query

when-to-use-natural-language-vs-semantic-sql

Config: app.platform.msoquery-tool-timeout

app.platform.semantic-sql-tool-timeout


References







Contact App Orchid | Disclaimer