Skip to main content
Glama
pratri

sap-mcp-server

by pratri

sap-mcp-server

An MCP server that exposes SAP S/4HANA OData services to a local LLM. It runs as a child process over stdio, listens on no port, and sends nothing off the machine except the SAP request itself.

It was built for LM Studio driving a local model, which is a harsher target than a hosted frontier model: a fraction of the context window, weaker instruction following, and no project file the client will read on your behalf. Most of what follows is a consequence of that.

The problem

Ask SAP's Business Partner service for five records and it answers with about 12,000 tokens of JSON. Measured against a live sandbox, that breaks down as:

Component

Share of payload

__deferred navigation links

55%

Fields nobody asked for

26%

__metadata blocks

4%

Fields actually used

3%

The rest is JSON structure and indentation.

Navigation links are the bulk of it. Every record carries a roughly 200-character absolute URL for each related entity, 23 per business partner, 115 across five records. They exist so that a machine client can walk the entity graph on demand. A language model cannot walk anything; it can only call the tool it was handed. Everything supporting OData's navigation model is dead weight in this particular consumer, which is why an unusually large reduction was available.

The practical consequence: at LM Studio's default 8,192-token context, one Business Partner query overflows the window before the model has room to answer.

Related MCP server: SAP S/4HANA MCP Server

What it does

Tool

Parameters

Returns

get-business-partners

top, nameContains, fields

Cleaned rows

get-sales-orders

top, soldToParty, fields

Cleaned rows

get-purchase-orders

top, supplier, fields

Cleaned rows

list-entity-fields

entity

Field names available on that entity

get-sap-data

endpoint

Raw SAP JSON, any read-only OData path

The three typed tools build the query in code and clean the response before the model sees it: __metadata and __deferred dropped, /Date(1667260800000)/ rewritten as 2022-11-01, PT20H17M49S as 20:17:49.

Query, five records

Before

After

Factor

Business partners

12,036

294

41x

Sales orders

8,750

326

27x

Purchase orders

4,333

380

11x

Token counts are estimated from measured character counts at roughly 3.6 characters per token.

Ordering turned out not to be optional

Early on, the same question put to two different clients came back with completely different rows. The local model looked like it was inventing data.

It wasn't. Every business partner it listed was real, and every field matched once each key was looked up directly. The model had added $orderby=BusinessPartner desc on its own initiative, reading "top 5" as "highest 5" rather than "first 5". Both answers were correct, because OData guarantees no ordering at all without an explicit $orderby. The question had never been well defined.

$orderby is now fixed in code, per entity. The wider point is the one that shaped the rest of the design: anything the answer depends on belongs in the server, not in a prompt, because only the server is guaranteed to be there.

There is a debugging lesson in it too. Two clients disagreeing is not evidence that either is hallucinating. The way to tell is by identity, not comparison: take a key the model reported and fetch that record directly.

Design notes

Typed tools instead of one generic one. An earlier version exposed a single tool taking a free-text OData path, which works well with a large model and poorly with a small one: writing a correct URL is the easiest thing to get wrong. The typed tools reduce the decision to picking a tool and maybe a number. Path, $orderby and $select are no longer things a model can get wrong, because they are no longer things a model chooses. get-sap-data is still there for anything the typed tools do not model.

Conventions live in the tool schema. The original design kept query rules in a project instruction file, the kind some MCP clients load and others ignore entirely. That works until you change client, at which point the rules silently stop applying and the model starts guessing. Anything that matters now lives in the tool definitions, which is the one channel every client sees.

Endpoints are validated, not trusted. Every request carries Basic Auth credentials in a header, and axios lets an absolute URL override the configured baseURL. An unchecked endpoint would therefore let a caller point the tool at any host on the internet and receive those credentials. Tool arguments are model-generated, and a model can be influenced by text coming back from SAP itself, so this is not hypothetical. Endpoints must be a path on the configured host: full URLs, protocol-relative //host forms and backslash authorities are refused, while a path missing its leading slash is normalized rather than rejected.

Failures are made loud. An unknown field name returns an error naming the problem rather than quietly falling back to the defaults. A navigation property passed to fields is detected and reported, since SAP accepts it in $select and then returns a deferred link that the cleaner drops, which would otherwise hand back a row missing the field that was asked for. SAP's "Service cannot be reached" page is 9,291 characters of HTML; error bodies are reduced to the OData message where there is one and capped otherwise, so a failure cannot flood the context window.

Layout

server.ts        MCP server: SAP calls, tool definitions, entity config
lib/odata.ts     pure helpers: cleaning, normalization, validation
test/unit.ts     59 unit tests, no network
test/smoke.mjs   61 integration tests against a live SAP host
scripts/         generates the client config for the current machine

The split exists so the fiddly parts are testable without a server or a network. Date and time conversion, filter escaping and endpoint validation are all pure functions in lib/odata.ts.

Testing

npm test runs 120 checks.

The 59 unit tests cover the shapes that are awkward to provoke from a live system: pre-epoch dates, Edm.Time durations with components missing, oversized and binary error bodies, and a list of hostile endpoint strings.

The 61 integration tests spawn the server over stdio exactly as a client does and call every tool against a real SAP host. They discover their fixtures from whichever system is configured, reading a real business partner key and a real sold-to party rather than hardcoding one sandbox's data, so they stay meaningful elsewhere. A check with no data to exercise it reports SKIP instead of passing quietly.

Integration rather than mocks is deliberate. The failures worth catching here are a renamed OData service, an expired password, a firewall change: none of them show up against a mock.

Running it

Needs Node 18+ and credentials for an SAP S/4HANA system with the OData services enabled.

npm install
cp .env.example .env            # then fill in SAP_HOST, SAP_USER, SAP_PASS
npm test                        # proves the server and credentials work
npm run mcp-config -- --write   # registers the server with LM Studio

Restart LM Studio afterwards; it reads ~/.lmstudio/mcp.json only at startup. Then load a model with "trainedForToolUse": true and ask it something:

> Show me the top 5 business partners.

npm run mcp-config fills in absolute paths for the current machine, which is the step most easily got wrong by hand. Without --write it prints the block instead of writing it. The same block works for Claude Desktop and other MCP clients; only the location of the config file differs.

Limitations

Read-only. SAP Gateway requires a CSRF two-handshake for writes, which is not implemented, and an unauthenticated write would simply 403.

Every caller authenticates as the same SAP user, which is fine for a personal sandbox and wrong for anything shared. A real deployment needs principal propagation.

Three entities are modelled. Others are reachable through get-sap-data, which returns SAP's raw JSON, so they cost what the raw JSON costs.

Tested against a single S/4HANA system. Field names and available services vary between installations.

Built with

TypeScript on Node 18+, @modelcontextprotocol/sdk, axios, and zod for tool schemas. No build step: tsx runs the TypeScript directly, which keeps the client config down to a single command.

Available Tools

5 tools
get-business-partnersA

List Business Partners (customers, suppliers and organizations) from SAP S/4HANA. Returns the first N in ascending Business Partner ID order. By default returns ID, name, category, grouping, customer and supplier numbers, and creation details — pass fields for anything else.

ParametersJSON Schema
NameRequiredDescriptionDefault
topNoHow many records to return. Defaults to 5. Large values produce large results — roughly 60 tokens per record — so keep this small unless you need the whole set. An empty result array means nothing matched.
fieldsNoField names to return instead of the default set (this becomes the OData $select). Use list-entity-fields to see what is available.
nameContainsNoOnly return partners whose full name contains this text.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the burden of behavioral disclosure. It states that results are limited to the first N records, ordered ascending by Business Partner ID, and describes the default field set and how to override it via the `fields` parameter. This is strong coverage of the tool's observable behavior, though it doesn't mention response envelope or failure semantics beyond what the schema covers.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences front-load the core behavior and default output, then direct the user to `fields` for extensions. No filler or redundant restating of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description sufficiently covers the main return behavior, ordering, and default projection. Given the simple parameter set and lack of nested objects, it is adequately complete. A small gap is the lack of any explicit statement about empty results or pagination, but those are partially handled by the `top` parameter description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters. The description adds context for `fields` ('pass fields for anything else') and implies `top` through 'Returns the first N', but it doesn't add meaningful semantics beyond the schema for `nameContains`. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb ('List') and resource ('Business Partners from SAP S/4HANA'), clarifies that it covers customers, suppliers, and organizations, and notes the ordering by Business Partner ID. This clearly differentiates it from sibling tools like get-sales-orders and get-purchase-orders.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the intended use clear: retrieving business partner master data. While it doesn't explicitly say 'use get-sap-data for other entities,' the resource name and sibling tool names provide enough context for an agent to select it appropriately for business partner list queries.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get-purchase-ordersA

List Purchase Orders from SAP S/4HANA, including supplier and address. Returns the first N in ascending Purchase Order number order. Pass fields to return different columns.

ParametersJSON Schema
NameRequiredDescriptionDefault
topNoHow many records to return. Defaults to 5. Large values produce large results — roughly 60 tokens per record — so keep this small unless you need the whole set. An empty result array means nothing matched.
fieldsNoField names to return instead of the default set (this becomes the OData $select). Use list-entity-fields to see what is available.
supplierNoOnly return purchase orders for this supplier number.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden and does useful work: it discloses that results are the first N records, that ordering is ascending by Purchase Order number, and that `fields` changes which columns are returned. It does not discuss auth or side effects, but the read-only nature is clear from 'List' and 'Returns'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, each earning its place: purpose, ordering behavior, and field customization. The most important information is front-loaded and there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only list tool with three optional parameters and a rich schema, the description covers purpose, ordering, default columns, and field selection. It does not explain the exact return shape, but 'Returns the first N' plus the schema's empty-array note is sufficient for an agent to invoke and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3 and the description need not repeat parameter details. The description adds useful context for `fields` by stating it returns different columns, but it does not add meaning beyond the schema for `top` or `supplier`.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List Purchase Orders from SAP S/4HANA') and even names the default return content ('including supplier and address'). The resource is clearly distinct from sibling tools like get-sales-orders and get-business-partners.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is implied by the resource name and origin, but the description does not explicitly say when to use this tool versus alternatives or when not to use it. The `fields` schema hint mentions list-entity-fields, but no explicit routing guidance appears in the description itself.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get-sales-ordersA

List Sales Orders from SAP S/4HANA, including net value, currency and processing status. Returns the first N in ascending Sales Order number order. Pass fields to return different columns.

ParametersJSON Schema
NameRequiredDescriptionDefault
topNoHow many records to return. Defaults to 5. Large values produce large results — roughly 60 tokens per record — so keep this small unless you need the whole set. An empty result array means nothing matched.
fieldsNoField names to return instead of the default set (this becomes the OData $select). Use list-entity-fields to see what is available.
soldToPartyNoOnly return orders for this sold-to party (customer) number.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are present, so the description carries the disclosure burden. It usefully states that results come back as the first N records in ascending Sales Order number order and explains the effect of the `fields` parameter. It does not cover pagination or error behavior, but for a list operation it discloses the key non-obvious behaviors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three concise sentences: the first states the core purpose, the second adds ordering and limit semantics, and the third flags column customization. Every sentence earns its place and there is no redundant restating of schema details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only list tool with three optional parameters and no output schema, the description covers the resource, notable output fields, ordering, and limiting behavior. Parameter specifics are fully delegated to the schema, so nothing essential is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds only a brief note that `fields` changes returned columns; the schema already explains `top` and `soldToParty` thoroughly. This meets the minimum but does not go far beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'List Sales Orders from SAP S/4HANA.' It also names concrete included data (net value, currency, processing status), which makes the tool's purpose unmistakable and distinguishes it from siblings like get-purchase-orders.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear usage context: use this tool when you need Sales Orders from SAP S/4HANA. It does not explicitly name alternative tools or exclusion conditions, but the resource-specific language makes selection unambiguous relative to the sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get-sap-dataA

Fetch data from any SAP S/4HANA OData path (read-only, GET only). Prefer get-business-partners / get-sales-orders / get-purchase-orders when they fit — they return far less data. Use this for other entities, entity-key reads like A_BusinessPartner('1'), navigation properties, $expand, $count and $metadata. Returns raw SAP JSON including __metadata, so keep $top small.

ParametersJSON Schema
NameRequiredDescriptionDefault
endpointYesPath on the configured SAP host, starting with '/'. Not a full URL. e.g. /sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top=5

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full burden. It discloses the operation is read-only and GET-only, warns that output is raw SAP JSON including __metadata, and advises keeping $top small. This covers the main safety and payload traits an agent needs; it does not mention error behavior or pagination, but those are less critical here.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four dense, purposeful sentences with no filler. The core purpose and safety trait are front-loaded, and every sentence adds either a routing rule, a usage case, or an important behavior warning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a generic OData fetch tool with a single parameter and no output schema, this description is nearly complete: it names when to use alternatives, enumerates intended use cases, describes the return shape, and warns about response size. The only notable omissions are error handling or pagination details, which are minor for this tool's purpose.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents the endpoint parameter at 100% coverage, so the baseline is 3. The description adds value by giving concrete endpoint examples and usage patterns such as A_BusinessPartner('1'), navigation properties, $expand, $count, and $metadata, which clarify what belongs in the parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description opens with a specific action on a concrete resource: 'Fetch data from any SAP S/4HANA OData path (read-only, GET only).' It also explicitly distances itself from sibling tools by name, so an agent can tell exactly what this generic endpoint tool is for.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit routing guidance: prefer get-business-partners, get-sales-orders, or get-purchase-orders when applicable because they return less data, and use get-sap-data for other entities, key reads, navigation, $expand, $count, and $metadata. This is a clear when-to-use vs. alternatives statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list-entity-fieldsA

List the field names available on a SAP entity. Call this before passing fields to one of the get-* tools, instead of guessing field names.

ParametersJSON Schema
NameRequiredDescriptionDefault
entityYesWhich entity to describe.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses the read-only, metadata-listing nature ('List the field names') and the intended sequencing ('before passing fields to get-* tools'). It does not mention return format or error behavior, but for a simple enum-limited tool this is adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no filler. The core purpose is front-loaded in the first sentence, and the second sentence adds immediate actionable usage guidance. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with one parameter and no output schema, the description covers what it does and when to call it. It could be more complete by stating the return shape explicitly, but the phrase 'List the field names' sufficiently conveys the output, and the sibling references orient the agent well.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% since the 'entity' parameter includes an enum and its own description. The tool description adds context beyond the schema by explaining that the parameter selects which entity's field names to retrieve for use with get-* tools, enriching the parameter's semantic role.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function with a specific verb and resource: 'List the field names available on a SAP entity.' It distinguishes itself from sibling data-retrieval tools by positioning itself as a helper for the get-* tools, so an agent immediately understands this returns metadata, not entity data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit usage guidance is given: 'Call this before passing `fields` to one of the get-* tools, instead of guessing field names.' This tells the agent when to invoke the tool and frames it as the alternative to guessing, which is clear direction for tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 5 tool updatesv2.0.0
    • First observedget-business-partners
    • First observedget-purchase-orders
    • First observedget-sales-orders
    • First observedget-sap-data
    • First observedlist-entity-fields

TDQS

A4.3/5.0
Disambiguation4/5

The three entity-specific getters are clearly distinct, and list-entity-fields serves an obvious separate purpose. get-sap-data overlaps with all getters, but its description clearly positions it as a fallback for other entities and advanced OData queries, so confusion is unlikely.

Naming Consistency4/5

The first three tools consistently use get-<plural-resource>, and get-sap-data follows the get- prefix. list-entity-fields is the only deviation, using list instead of get, but the pattern is still readable and predictable overall.

Tool Count5/5

Five tools is well-scoped for a read-only SAP S/4HANA connector. Each tool has a clear role, and the generic get-sap-data tool avoids the need to multiply entity-specific tools.

Completeness5/5

The server covers common entities with dedicated tools and provides a generic OData path for everything else, plus field discovery to support custom field requests. For a read-only data access server, the surface is complete with no obvious dead ends.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Exposes SAP S/4HANA OData services as tools for LLMs, enabling users to list and create sales orders via the Model Context Protocol. It integrates with SAP BTP using the SAP Cloud SDK to provide secure access to enterprise data through natural language.
    -
  • F
    license
    A
    quality
    C
    maintenance
    Enables interaction with SAP S/4HANA systems via OData, allowing service discovery, metadata exploration, field value retrieval, and CRUD operations through natural language.
    4
    5
    -

Latest Blog Posts

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/pratri/sap-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server