ariba-mcp
Provides tools for interacting with SAP Ariba APIs, enabling AI agents to access Ariba procurement and sourcing operations exposed via the OpenAPI specification.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ariba-mcpGet purchase orders for supplier Acme Corp"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Metadata-Driven SAP Ariba MCP Server (PoC)
A proof-of-concept MCP server that exposes selected SAP Ariba OpenAPI operations as MCP tools.
Which operations become tools is configuration, not code: point it at a downloaded OpenAPI
file, list the operationIds you want in config/tools.yaml, restart. No per-operation Python.
1. What this PoC does
Client app
→ Entra access token (who is calling this MCP server)
→ MCP server (validates the token, builds the request from OpenAPI)
→ Ariba OAuth token + API key (how this server authenticates to Ariba)
→ SAP Ariba APITwo independent identities are involved, and it matters that you keep them apart:
Microsoft Entra ID authenticates the caller to the MCP server. Every MCP request must carry a valid Entra access token for this API, holding the required delegated scope.
Ariba OAuth authenticates the MCP application to Ariba. The server holds one set of Ariba client credentials and one API key, and uses them for every call.
There is no named-user propagation in this PoC. Ariba sees the same technical identity no matter who called the tool. Ariba has no idea which Entra user triggered a request; the Entra identity appears only in this server's logs.
The server:
Serves MCP over Streamable HTTP.
Validates Entra bearer tokens (signature, issuer, audience, tenant, lifetime, scope).
Loads one local OpenAPI 3.0/3.1 JSON or YAML file at startup.
Exposes only the operations you explicitly enable.
Generates each tool's MCP input schema from the OpenAPI operation.
Validates tool arguments against that schema.
Acquires and caches an Ariba OAuth token.
Calls Ariba with the OAuth token and the API key.
Returns a normalized structured response with a correlation ID.
Related MCP server: swaggbot
2. Architecture
Authorization: Bearer <entra-access-token>
MCP client ─────────────────────────────────────────────▶ POST /mcp
│
┌───────────────────────────────────────────────────────────────▼───────────┐
│ Starlette app (built by the MCP SDK) │
│ │
│ GET /health ──────────────────────────────▶ {"status":"ok"} (no auth) │
│ │
│ /mcp ─▶ BearerAuthBackend ─▶ RequireAuthMiddleware ─▶ MCP session │
│ │ │ │ │
│ │ 401 invalid token │ 403 missing scope │ │
│ ▼ ▼ ▼ │
│ EntraTokenVerifier on_list_tools │
│ (OpenID config + JWKS, on_call_tool │
│ both cached) │ │
└──────────────────────────────────────────────────────────────┼───────────┘
│
built once at startup ▼
OpenAPI file ─▶ OpenApiLoader ─▶ RefResolver ─▶ SchemaConverter ─▶ ToolRegistry
tools.yaml ─────────────────────────────────────────────────────▶ │
▼
ToolExecutor
(validate ▸ bind ▸ call ▸ normalize)
│
AribaClient ◀──────────┘
│ Authorization: Bearer <ariba-token>
│ apiKey: <ariba-api-key>
▼
SAP Ariba API3. Project structure
Path | Purpose |
| Startup wiring: builds the registry once, mounts |
| Typed environment settings; exits with a readable message when configuration is invalid. |
| The |
| Key-value logging helpers; only ever receives non-secret fields. |
| Validates Entra access tokens and extracts the caller context. |
| Acquires, caches and refreshes the Ariba application OAuth token. |
| Loads the OpenAPI file and indexes operations by |
| Immutable snapshots of an operation, its parameters and request body. |
| Resolves local |
| Turns an operation into an MCP input schema plus argument bindings. |
| Reads |
| The one execution path shared by every tool. |
| The long-lived HTTP client; owns all security-sensitive headers. |
| Which operations are exposed, under what names, with which parameters. |
| Mock spec so the PoC runs without the real SAP file. |
4. Prerequisites
Python 3.12
A Microsoft Entra tenant where you can register applications
An app registration that exposes this MCP API (the "API app registration")
An app registration for the client that will call the MCP server
An SAP Ariba Developer Portal application, with access approved for the API you want
The downloaded OpenAPI JSON/YAML for that API
Ariba OAuth client ID and secret, and the application (API) key
5. Microsoft Entra setup
Register the MCP API. Entra admin centre → App registrations → New registration. Note its Application (client) ID — this is
ENTRA_API_CLIENT_ID, and the Directory (tenant) ID — this isENTRA_TENANT_ID.Expose the API. On the API registration → Expose an API → set the Application ID URI (
api://<client-id>is the default).Create the delegated scope
ariba.access(Add a scope → admin/user consent text → enabled).Register the client application that will call the MCP server.
On the client → API permissions → My APIs → select the MCP API → Delegated permissions → tick
ariba.access.Grant admin consent if your tenant requires it.
Configure a redirect URI on the client that matches its type (for a desktop/CLI test client,
http://localhostas a Mobile and desktop platform).Obtain an access token for the scope
api://<ENTRA_API_CLIENT_ID>/ariba.access(see §10).
The server expects an access token, not an ID token, and checks these claims:
Claim | Expected |
|
|
| The |
| Exactly |
| Required; |
| Space-separated; must contain |
| Must be currently valid (60s leeway by default) |
A token with no scp claim is rejected outright, which is what keeps an ID token from being
accepted even if its audience happens to match.
6. SAP Ariba setup
Create or reuse an application in the SAP Ariba Developer Portal.
Request access to the API you need and wait for approval.
Copy the application key — this is
ARIBA_API_KEY.Generate the OAuth credentials —
ARIBA_CLIENT_IDandARIBA_CLIENT_SECRET.Note the token URL (
ARIBA_TOKEN_URL) and the API base URL (ARIBA_BASE_URL) for your region and environment.Download the OpenAPI JSON/YAML for that API.
Put the file under
specs/.Set
ARIBA_OPENAPI_FILEto its path.
The exact token parameters and the API-key header name differ between Ariba APIs and between
application configurations. Check your API's documentation and adjust ARIBA_API_KEY_HEADER
(commonly apiKey) and ARIBA_TOKEN_AUTH_STYLE. See §15 for what this PoC assumes.
7. Local setup
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # Windows: copy .env.example .envThen edit .env:
ENTRA_TENANT_ID/ENTRA_API_CLIENT_IDfrom §5.ARIBA_*values from §6.Leave
ARIBA_OPENAPI_FILEas the example spec for a first run.Set
MCP_ALLOWED_HOSTSif you bind to anything other than localhost (see §9).
.env is in .gitignore. Do not commit it.
8. Configure tools
config/tools.yaml maps operation_id (an operationId from the OpenAPI document) to an MCP
tool:
tools:
- operation_id: searchCatalogItems # must exist in the OpenAPI file
tool_name: search_catalog_items # the MCP tool name; must be unique
enabled: true # only enabled operations are exposed
read_only: true # advertised as an MCP read-only hint
description: >
Search SAP Ariba catalog items using approved structured search
parameters. Use this when a requester is looking for products.
allowed_parameters: # only these parameters are exposed
- query
- supplierId
- commodityCode
- limit
defaults: # applied when the caller omits the argument
limit: 10
limits: # may only tighten the OpenAPI schema
limit:
maximum: 25Rules:
Only operations with
enabled: truebecome tools.Every
operation_idmust exist in the OpenAPI document — including disabled ones, so a typo fails at startup rather than the day somebody enables it.allowed_parametersis an allowlist. Anything not listed is invisible to callers. Path parameters are always exposed, since the URL cannot be built without them.defaultsandlimitsmust refer to parameters that are actually exposed.limitscan only make a constraint stricter. Configuringmaximum: 500against a spec that saysmaximum: 50leaves 50 in place.descriptionoverrides the OpenAPI description. If both are absent, startup fails.aliasesrenames an awkward parameter ({"$top": "top"}) while still sending the original name.Restart the server after any change — the configuration is read once at startup.
Optional key not shown above: aliases.
9. Run the server
python -m app.mainExpected routes:
Route | Auth | Purpose |
| none | Liveness. Returns |
| Bearer | MCP Streamable HTTP requests. |
| Bearer | MCP Streamable HTTP server-to-client stream. |
| none | Resource metadata added by the SDK. |
On a successful start you will see:
INFO ariba_mcp event=server_started tools=search_catalog_items,get_catalog_item openapi_file=specs/ariba-api.example.yaml
INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)A note on MCP_ALLOWED_HOSTS. The MCP SDK's DNS-rebinding protection is enabled
automatically only when the server binds to localhost. If you bind to 0.0.0.0 (the default, and
what the container does) that protection is off unless you list the hostnames clients use:
MCP_ALLOWED_HOSTS=ariba-mcp.example.com,localhost:*Requests whose Host header is not on the list are then rejected with HTTP 421.
10. Obtain a test Entra token
Use your registered client application with MSAL and an interactive sign-in. Save this as
get_token.py outside the repository, or run it in a scratch directory:
# pip install msal
import msal
TENANT_ID = "<your-tenant-id>"
CLIENT_ID = "<your-CLIENT-app-client-id>" # the client, not the API
SCOPE = ["api://<your-API-app-client-id>/ariba.access"]
app = msal.PublicClientApplication(
CLIENT_ID, authority=f"https://login.microsoftonline.com/{TENANT_ID}"
)
result = app.acquire_token_interactive(scopes=SCOPE)
print(result["access_token"])Keep the token out of your shell history — assign it in a way your shell does not record
(a leading space works in bash/zsh with HISTCONTROL=ignorespace), or have your MCP client
fetch it directly. Paste it into jwt.ms to confirm aud, tid and scp
match what §5 describes before you blame the server.
11. Test the MCP server
This is an MCP endpoint, not a REST API — talk to it with an MCP client that supports Streamable
HTTP and a bearer token, not with plain curl calls to tool URLs (there are none). Configure your
client with:
Endpoint:
http://localhost:8000/mcpHeader:
Authorization: Bearer <entra-access-token>
With the official Python SDK:
import asyncio, httpx2
from mcp.client.client import Client
from mcp.client.streamable_http import streamable_http_client
TOKEN = "<entra-access-token>"
async def main():
async with httpx2.AsyncClient(headers={"Authorization": f"Bearer {TOKEN}"}) as http:
transport = streamable_http_client("http://localhost:8000/mcp", http_client=http)
async with Client(transport, raise_exceptions=True) as client:
tools = await client.list_tools()
for tool in tools.tools:
print(tool.name, tool.input_schema)
result = await client.call_tool(
"search_catalog_items", {"query": "laptop", "limit": 5}
)
print(result.structured_content)
asyncio.run(main())tools/list returns the enabled tools with their OpenAPI-derived schemas:
{
"name": "search_catalog_items",
"description": "Search SAP Ariba catalog items using approved structured search parameters. ...",
"inputSchema": {
"type": "object",
"properties": {
"limit": {"type": "integer", "default": 10, "minimum": 1, "maximum": 25},
"query": {"type": "string", "minLength": 2, "description": "Free-text search term."},
"supplierId": {"type": "string"},
"commodityCode": {"type": "string"}
},
"additionalProperties": false,
"required": ["query"]
}
}A successful tools/call returns:
{
"data": {"items": [{"itemId": "I1", "description": "Laptop"}]},
"pagination": {"next_cursor": null, "has_more": false},
"metadata": {
"tool": "search_catalog_items",
"operation_id": "searchCatalogItems",
"http_status": 200,
"correlation_id": "3f2a..."
},
"warnings": []
}pagination appears only when the backend actually reported pagination. A failure returns
is_error: true with:
{
"error": {
"code": "ARIBA_API_ERROR",
"message": "SAP Ariba rejected the request.",
"retryable": false,
"correlation_id": "3f2a...",
"details": {"http_status": 400}
}
}You can still check liveness and auth with curl:
curl -i http://localhost:8000/health # 200 {"status":"ok"}
curl -i -X POST http://localhost:8000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' # 401, no token12. Run tests
pytest
pytest -qTests never contact Microsoft or SAP: Entra discovery/JWKS and every Ariba call are mocked with
respx, and test tokens are signed with an RSA key generated in-process.
13. Docker
docker build -t ariba-mcp-poc .
docker run --rm -p 8000:8000 --env-file .env ariba-mcp-pocConfiguration arrives through environment variables at run time. The image contains no secrets
and no .env (both are excluded by .dockerignore), and runs as a non-root user. For a single
override, add -e ARIBA_VERIFY_SSL=false and so on. To use a spec that is not baked into the
image, mount it: -v "$PWD/specs:/app/specs:ro".
14. Replace the example OpenAPI file
Copy your downloaded file into
specs/, e.g.specs/ariba-catalog.yaml.Set
ARIBA_OPENAPI_FILE=specs/ariba-catalog.yamlin.env.Inspect the available operation IDs:
python -c "from pathlib import Path; from app.openapi.loader import load_openapi_document; \ print('\n'.join(load_openapi_document(Path('specs/ariba-catalog.yaml')).operation_ids()))"Add the operations you want to
config/tools.yamlwithenabled: true.Start the server.
Resolve any
OPENAPI_SCHEMA_UNSUPPORTEDerrors. Each one names the operation, the location and the construct, e.g. "Operation 'createRequisitionDraft' uses the unsupported construct 'oneOf' at request body." Either pick a different operation, or narrow the exposed parameters withallowed_parametersso the unsupported part is not reachable.
Adding a compatible operation this way exposes a new MCP tool with no Python changes.
Unsupported OpenAPI constructs
These fail at startup for an enabled tool rather than being silently widened:
oneOf,anyOf,not,discriminatorRecursive schemas (a
$refthat reaches itself)Request bodies without an
application/jsoncontent typeallOfthat composes anything other than objectsEmpty/untyped schemas (
{})Cookie parameters, and HTTP methods other than GET/POST/PUT/PATCH
Types outside string, integer, number, boolean, array, object, null
nullable: true (OpenAPI 3.0) is normalized to a JSON Schema type union ["string", "null"],
which matches how OpenAPI 3.1 expresses it.
Objects that declare properties get additionalProperties: false unless the spec says
otherwise, so unknown fields are rejected before they reach Ariba. A free-form object (no declared
properties) stays open.
15. Assumptions about the Ariba OAuth request
Verified against SAP's documented shape but not against a live Ariba tenant:
POSTtoARIBA_TOKEN_URLwithgrant_type=client_credentials, form-encoded.Credentials as an HTTP Basic header,
Authorization: Basic base64(client_id:client_secret). SetARIBA_TOKEN_AUTH_STYLE=bodyto sendclient_id/client_secretas form fields instead.expires_inis honoured when present; otherwise a 10-minute lifetime is assumed. The token is refreshedARIBA_TOKEN_REFRESH_MARGIN_SECONDS(default 60) before it expires.Refresh tokens are not used. A client-credentials token is cheap to re-acquire, so the provider simply requests a new one. If your Ariba application requires a refresh-token flow,
AribaTokenProvider._build_token_requestis the single method to change.On a backend 401 the cached token is dropped and the request is retried once. A second 401 is returned as an error. Write requests are never retried after a 5xx.
16. Troubleshooting
Symptom | Cause and fix |
| No |
| Wrong audience. |
|
|
| You sent an ID token, not an access token. Request the |
| Valid token, but |
| The |
| Ariba rejected the client credentials. Verify |
| Usually the API key: check |
|
|
| An |
| See §14. The message names the operation, location and construct. |
SSL / certificate errors | A TLS-intercepting proxy. Point |
| Raise |
Every tool call logs one line with the correlation ID that also appears in the response
metadata, so you can match a client-side failure to a server-side log:
event=tool_call correlation_id=3f2a... tenant_id=... object_id=... tool=search_catalog_items \
operation_id=searchCatalogItems method=GET path=/catalog/items backend_status=200 \
duration_ms=142 status=okNote that path is the OpenAPI template, not the substituted URL, so caller-supplied
identifiers stay out of the logs.
17. Security limitations
This is a proof of concept, not a production service:
A single technical Ariba identity performs every backend call.
No named-user propagation. Ariba cannot attribute an action to the Entra user who caused it.
No production RBAC. Any caller holding
ariba.accesscan invoke every enabled tool; there is no per-tool authorization.In-memory token cache, so one server instance is assumed. Multiple replicas each hold their own Ariba token.
No persistent audit store. Audit information exists only in the process log.
Write tools should stay disabled unless you are deliberately testing them.
createRequisitionDraftships asenabled: falsefor this reason.Secrets come from environment variables, with no secret-manager integration and no rotation.
18. Future enhancements
Entra-to-Ariba identity resolution and named-user requester/preparer enforcement
App roles and tool-level RBAC
Dynamic metadata refresh instead of a restart
Azure Key Vault for secrets, and a distributed token cache for multiple replicas
Persistent audit storage
API-specific response normalization
Approval/confirmation controls in front of write operations
Note on the MCP SDK
The original brief called for FastMCP. Built against mcp==2.0.0, this uses the SDK's
low-level Server instead, for one blocking reason:
mcp.server.fastmcp no longer exists in 2.0 (it was renamed MCPServer), and neither that class
nor 1.x's FastMCP can advertise a hand-built input schema — both derive a tool's inputSchema
from its Python function signature via func_metadata, with no override. That is incompatible
with the core requirement that schemas come from OpenAPI.
mcp.server.lowlevel.Server takes on_list_tools / on_call_tool callables and passes each
types.Tool(input_schema=...) through untouched, and its streamable_http_app(...) supplies the
Streamable HTTP transport, the bearer-auth middleware and the unauthenticated /health route in
one supported call. Everything else in the brief is unchanged.
Two consequences worth knowing:
The low-level server does not validate
tools/callarguments against the advertised schema, soToolExecutorvalidates them withjsonschemabefore anything is sent to Ariba.The SDK depends on
httpx2. This project's own Ariba client useshttpx0.28 (so tests can mock it withrespx); both libraries coexist in one environment.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceTransforms SAP S/4HANA or ECC systems into conversational AI interfaces by exposing OData services as dynamic MCP tools. Enables natural language interactions with ERP data for querying, creating, updating, and deleting business entities.Last updated351MIT
- Alicense-qualityDmaintenanceTransforms Swagger/OpenAPI documented APIs into conversational interfaces, enabling natural language interaction with APIs through an MCP server for use with AI assistants.Last updated4MIT
- Alicense-qualityDmaintenanceA config-driven MCP server that exposes OData and REST APIs as MCP tools, enabling AI assistants to query, manage, and monitor SAP backends through natural language.Last updated7126MIT
- Alicense-qualityCmaintenanceA generic MCP server that converts any OpenAPI/Swagger specification into MCP tools, enabling AI assistants to search, explore, and execute REST APIs.Last updatedMIT
Related MCP Connectors
MCP server for AI access to SmartBear tools, including BugSnag, Reflect, Swagger, PactFlow, QTM4J.
Official Microsoft MCP Server to query Microsoft Entra data using natural language
Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/shafquat-quadar/ariba-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server