Skip to content

About

BTP MCP Server

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

BTP MCP Server

An MCP (Model Context Protocol) server for SAP BTP administration, powered by odata-mcp-proxy. It exposes BTP Core Services APIs as MCP tools, allowing AI assistants like Claude to manage your BTP landscape through natural language — and ships interactive MCP-UI dashboards (subaccount overview with drill-down, a geomap of data-center locations, consumption and users/roles dashboards) rendered directly in the chat.

There is no application code in this project. Both the API tools and the interactive views are declared in a single JSON config file (btp-admin-api-config.json) alongside self-contained HTML templates in ui/. npm start runs the odata-mcp-proxy binary against that config.

How It Works

This project uses the odata-mcp-proxy npm package, which maps OData/REST services to MCP tools based on a configuration file. You provide a config describing your APIs and entity sets, and the proxy generates the corresponding MCP tools automatically.

The config's ui section declares five additional UI_* tools. For each one the proxy fetches the listed data sources concurrently through the same OData clients (forwarding the caller's JWT), inlines the shared CSS/JS partials into the named template, bakes the payload in, and returns it as an mcp-ui embedded resource — with the MCP Apps adapter enabled, so it renders on both MCP Apps hosts like Claude and classic mcp-ui hosts.

AI Assistant (Claude, Cursor, etc.)
        |
        | MCP Protocol (HTTP or stdio)
        v
  odata-mcp-proxy ──[apis]──> entity tools (Subaccounts_list, …)
        |         └─[ui]────> UI_* tools + ui:// templates (ui/*.html)
        |
        | REST + OAuth2 (via BTP Destination Service)
        v
  BTP Core Services / Usage Data Management / XSUAA APIs

The server stays declarative: it fetches and injects, but does no aggregation. Rolling raw API responses up into charts and tables is each template's job.

Think of it like the SAP Application Router -- a ready-made runtime you configure, not code you write.

Interactive UI Tools (MCP-UI)

Five tools return interactive HTML views instead of raw JSON. Each result contains a short text summary for the model plus a ui:// embedded resource the host renders in an iframe; buttons and table rows inside the views call the next view's tool and swap it into the frame, so you can navigate between views without going back through the model.

Tool What it shows Navigation
UI_SubaccountsOverview Stat tiles, subaccounts-per-region chart, searchable/sortable table of all subaccounts Row click → UI_SubaccountDetails; header buttons → the dashboards
UI_SubaccountDetails One subaccount: state, region/location, labels, and its entitled services & plans (quotas) Back to overview / map
UI_GeomapDashboard World map with data-center locations sized by subaccount count, plus table view and unmapped-region list Circle/row click → subaccounts at that location → details
UI_ConsumptionDashboard Monthly usage trends per service/plan/metric (small multiples), service adoption, and cost-per-month stacked by subaccount when cost data is available Window presets (3/6/12 months) re-run the tool
UI_UsersRolesDashboard Users per identity provider, role collections by assigned users, searchable users / role-collections tables —

Notes:

  • All views are self-contained (no CDN dependencies), respect light/dark mode, include tooltips, and every chart has a table twin.
  • The same templates are also registered as ui://btp-mcp-server/* resources with tool metadata (ui/resourceUri) for MCP Apps hosts that pre-fetch templates; tool results carry the data as structuredContent as well.
  • ui/_shared.js talks to the host itself rather than relying on the adapter @mcp-ui/server injects: it probes with a ui/initialize handshake and, on MCP Apps hosts (Claude), sends tools/call and size updates as JSON-RPC — the adapter bridges those by patching parent.postMessage, which throws in the cross-origin sandboxed frame those hosts use, so every click was dropped. Hosts that stay silent get classic mcp-ui tool messages instead and render the next view themselves. Because a view's script can run again in a frame that already ran it, injected partials must not declare top-level const/let.
  • UI_ConsumptionDashboard needs the BTP_USAGE_SERVICE destination (Usage Data Management API); cost data additionally requires a consumption-based contract. Its reporting window is derived in the config via {$monthsAgo(months-1):yyyymm} / {$now:yyyymm}, so the months input (1–24, default 6) needs no server-side date code.
  • UI_UsersRolesDashboard uses the XSUAA SCIM and Authorization APIs via BTP_XSUAA_SERVICE. The SCIM collection is paged by the proxy (paginate: 100 per request, capped at 500); when the cap is hit the view says so rather than under-reporting.
  • Geo coordinates for the map come from a BTP region table (ui/_regions.js), injected into the templates as a partial; unknown regions are listed under the map instead of being dropped.

Tool surface (progressive discovery)

Registering every operation of every entity set as its own tool produced 40 tools and ~8,600 tokens of tools/list — around 4% of a 200k context window before the user has said anything, and past the point where models start picking worse tools.

The config enables the proxy's hybrid discovery mode instead:

"discovery": {
  "mode": "hybrid",
  "alwaysRegister": ["Subaccounts"],
  "maxResults": 25,
  "maxFullResults": 5
}
Tools tools/list
One tool per operation (previous) 40 ~8,600 tokens
hybrid, Subaccounts pinned (current) 12 ~2,610 tokens
search (nothing pinned) 7 ~1,441 tokens

What stays directly visible: the five Subaccounts_* tools (the hot path for mutations), the five UI_* views (the entry points the model should prefer), and two meta-tools — search_operations to find the right api/entitySet/operation, and execute_operation to run it. Everything else is reachable through those two, plus one odata://{api}/{entitySet} schema resource per entity set for hosts that pre-fetch resources.

Remove the discovery block and every entity set goes back to having its own tools; nothing else in the config changes.

Exposed BTP APIs

The config file (btp-admin-api-config.json) defines three BTP Core Services and the XSUAA Authorization API:

Account Management

Tool Operations Description
GlobalAccount list View global account details
Subaccounts list, get, create, update, delete Manage subaccounts
Directories list, get, create, update, delete Organize subaccounts into directories

Entitlements

Tool Operations Description
Assignments list, update Manage service plan entitlement assignments
AllowedDataCenters list View available data centers

Provisioning

Tool Operations Description
EnvironmentInstances list, get, create, delete Manage runtime environments (e.g. Cloud Foundry orgs)
AvailableEnvironments list View environment types available for provisioning

Consumption (Usage Data Management)

Tool Operations Description
MonthlyUsage list Monthly service usage per subaccount/service/plan/metric (?fromDate=YYYYMM&toDate=YYYYMM)
MonthlySubaccountsCost list Monthly cost per subaccount for consumption-based licenses

Authorization (XSUAA)

Tool Operations Description
Users list, get Subaccount users via the XSUAA SCIM API (SCIM query params, not OData)
Applications list, get View XSUAA application registrations
RoleCollections list, get, create, update, delete Manage role collections
Roles list, get, create, delete Manage roles derived from role templates
RoleTemplates list, get View role templates defined by applications

All _list tools support OData query parameters: $filter, $select, $expand, $orderby, $top, $skip.

Claude Code Skill

The repository ships a Claude Code skill at .claude/skills/btp-admin/SKILL.md that teaches Claude how to use these tools efficiently:

  • Tool selection -- maps common admin tasks ("entitle a service", "create a CF org") to the right tool on the first try
  • Token discipline -- always use $select, $filter, and $top so verbose BTP list responses stay small
  • Payload shapes -- documents request bodies the JSON schemas can't fully express (entitlement amount vs enable, environment instance parameters, role keys)
  • Workflows and pitfalls -- multi-step recipes (subaccount onboarding) and async-operation handling

Skills use progressive disclosure: only the name and description are loaded into context until a BTP admin task triggers the full skill, so the standing cost is a few dozen tokens. Anyone cloning this repository and using Claude Code gets it automatically; it also works when the repo is added as a project in claude.ai.

Prerequisites

  • Node.js 18+ (20+ recommended)
  • SAP BTP account with a Cloud Foundry environment
  • BTP Destinations configured for the CIS APIs (Accounts, Entitlements, Provisioning) with OAuth2 authentication
  • Cloud Foundry CLI (cf) and MBT Build Tool (mbt) for deployment

Project Structure

btp-mcp-server/
├── package.json                # Start script + the single odata-mcp-proxy dependency
├── btp-admin-api-config.json   # Everything: `apis` (entity tools) + `ui` (view tools)
├── ui/                         # Self-contained HTML view templates
│   ├── subaccounts-overview.html
│   ├── subaccount-details.html
│   ├── dashboard-geomap.html
│   ├── dashboard-consumption.html
│   ├── dashboard-users-roles.html
│   ├── _shared.css / _shared.js#   chart chrome, palette, helpers (injected as partials)
│   ├── _regions.js             #   BTP region → city/coordinates table (injected)
│   └── _world-path.txt         #   world outline (Natural Earth 110m, public domain)
├── .claude/skills/btp-admin/   # Claude Code skill: tool selection + token-usage guidance
├── mta.yaml                    # BTP Cloud Foundry deployment descriptor
├── xs-security.json            # XSUAA OAuth2 configuration
├── default-env.json            # Local dev credentials (gitignored)
└── LICENSE

Getting Started

1. Install dependencies

npm install

2. Configure BTP destinations

Create BTP Destinations pointing to the CIS, Usage Data Management and XSUAA APIs:

Destination URL Used by
BTP_ACCOUNTS_SERVICE https://accounts-service.cfapps.<region>.hana.ondemand.com Account tools, all UI views
BTP_ENTITLEMENTS_SERVICE https://entitlements-service.cfapps.<region>.hana.ondemand.com Entitlement tools, subaccount details UI
BTP_PROVISIONING_SERVICE https://provisioning-service.cfapps.<region>.hana.ondemand.com Environment tools
BTP_USAGE_SERVICE https://uas-reporting.cfapps.<region>.hana.ondemand.com Consumption tools + dashboard
BTP_XSUAA_SERVICE https://<subdomain>.authentication.<region>.hana.ondemand.com Authorization tools, users & roles dashboard (XSUAA apiaccess service key)

Each destination should use OAuth2 client credentials authentication.

3. Local development

Create a default-env.json with your BTP service bindings (XSUAA, Destination, Connectivity) to run locally:

npm start

This runs odata-mcp-proxy --config btp-admin-api-config.json, which registers the entity tools from the config's apis section and the interactive views from its ui section.

4. Deploy to BTP

npm run build:btp     # Build MTA archive
npm run deploy:btp    # Deploy to Cloud Foundry

The MTA deployment provisions three service instances:

  • Destination (lite) -- resolves API endpoints and manages OAuth2 tokens
  • Connectivity (lite) -- enables secure backend connectivity
  • XSUAA (application) -- handles OAuth2 authentication with role-based access control

Security

The XSUAA configuration (xs-security.json) defines three role templates:

Role Scopes Description
MCPViewer read Read-only access
MCPEditor read, write Read and write access
MCPAdmin read, write, admin Full administrative access

OAuth2 redirect URIs are pre-configured for Claude.ai, Cursor, Microsoft Teams, and local development.

Creating Your Own MCP Server

This project demonstrates how easy it is to create a custom MCP server using odata-mcp-proxy. To build your own:

  1. Create a new project and install the dependency:

    mkdir my-mcp-server && cd my-mcp-server
    npm init -y
    npm install odata-mcp-proxy
  2. Add a start script to package.json:

    {
      "scripts": {
        "start": "odata-mcp-proxy --config my-api-config.json"
      }
    }
  3. Define your APIs in a config file (my-api-config.json):

    {
      "server": {
        "name": "my-mcp-server",
        "version": "1.0.0",
        "description": "My custom MCP server"
      },
      "apis": [
        {
          "name": "my-api",
          "destination": "MY_BTP_DESTINATION",
          "pathPrefix": "/api/v1",
          "csrfProtected": true,
          "entitySets": [
            {
              "entitySet": "Products",
              "description": "Product catalog",
              "category": "master-data",
              "keys": [{ "name": "Id", "type": "string" }],
              "operations": { "list": true, "get": true, "create": false, "update": false, "delete": false }
            }
          ]
        }
      ]
    }
  4. Add your mta.yaml, xs-security.json, and BTP Destinations, then deploy. That's it -- no code to write.

License

MIT

About

BTP MCP Server

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages