$metadata; nothing asks you to choose, because one SAP system serves both and the same ABAP service can be bound as either.
The service, entity and operation are fixed when you set the tool up, along with any field values you pin there. The agent supplies only the business values you leave to it.
Authentication and enablement
Requires a SAP connection under Connectors, using Basic Auth or OAuth 2.0 (client credentials). The base URL, SAP client, service path and credentials all come from that connection; nothing about the target system is ever supplied by the agent. Off by default. Add it to an agent from the Tools tab; it has its own full-page setup flow (see Configuration) and cannot be saved until that flow is complete. A tool that has not been configured fails with “this tool has not been configured yet”.Connection fields
Both paths must be absolute paths starting with
/. A full URL, a query string or fragment, spaces, or a bare / is rejected when the connection is saved. The Service Catalog Path (V4) (catalog_path_v4) is a third Advanced field on the same terms — it defaults to /sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002/ServiceGroups, and blank turns V4 catalog search off. There is no V4 service path field: a V4 service is addressed by its whole path, so there is no prefix to override. A tool run never reads any catalog.
Choosing the service
A service is identified by one of two forms:- A bare name such as
API_SALES_ORDER_SRV(a V2 service), joined to the OData Service Path. Keep any;v=2suffix the catalog shows; it is part of the identifier. - A path: anything containing a
/is taken as an absolute path from the host root. This is how a V4 service is addressed (sap/opu/odata4/sap/<group>/srvd/sap/<service>/0001), and also a V2 service in a customer namespace or behind a middleware address.
://), a .. segment, whitespace, ?, # or \ is refused.
The picker suggests services from the catalogs, but the catalog is a suggestion, not a gate. A service is usable if its $metadata can be read, whether or not any catalog lists it. Typing a service name and verifying it always works, and so does pasting a working curl command (see Configuration), including when both catalogs are unreadable.
How the catalogs are searched:
- V2: the text you type is pushed down to SAP as a name filter on
TechnicalServiceName, so a search reads a handful of rows instead of every published service. - V4: the catalog cannot be narrowed on the server, so the whole list is fetched once and filtered locally.
- Release status and service type travel with each row and are narrowed locally in both. The V2 gateway answers such filters with an empty list and the V4 catalog ignores them, so they are never sent.
- Each version returns 100 services per page, and pages accumulate as you scroll. A catalog is cached for 10 minutes, failures included, keyed on the connection’s identity — its host, its OData and catalog paths, its SAP client and its credentials — so two connections to the same host do not share one another’s catalog.
v2_reason, v4_reason) and still returns whatever the other one gave. It reports available: false only when neither answered.
If the first page of a catalog fails, that catalog counts as unavailable. If a later page fails, a next-page link cannot be followed, or the 50-page limit is reached, the rows already read are kept and
v2_partial or v4_partial says why, so a service missing from the list may simply not have been read. A partial catalog is cached for one minute, not ten. When neither catalog answered, the single reason is the V2 one, unless V2 is only “not configured”, in which case it is the V4 one.
Configuration
A SAP OData tool is set up in its own dialog, because what it asks depends on the entity you pick. Open it from the SAP OData tool in the Tools tab: Configure parameters the first time, Add configuration for further ones, and Edit configuration (or the configure icon) on an existing one. As with a custom connector, Edit parameters on an existing configuration opens the native parameter editor instead — the name, description and SAP connection — with an Edit configuration button through to the dialog. There are three steps. Next is enabled when the current step is complete, and a finished step can be reopened from the header. While you work, the configuration is re-checked against the service’s live$metadata, and everything you are offered — entities, field names, labels, lengths, the operations an entity permits — comes from that document. A saved tool that no longer compiles (a field was renamed in SAP, or the connection changed) reopens on the step that explains why. Closing with unsaved changes asks you to confirm.
1. Source
Give the tool a name (saved in snake_case) and an optional description saying when the agent should reach for it. Then pick the SAP connection, explicitly: on a workspace with a sandbox and a production system, every entity and field below is read live from the one you choose. When the workspace has only one SAP connection it is picked for you. Choose the service: search the catalogs, type a name and verify it (see Choosing the service), or open Start from a call you already have and paste a curl command. The service, entity, operation, filters, keys,$select, $expand, $top, $orderby and request body are read out of the command and filled in, where every one of them stays editable. The address in the command is ignored — the connection supplies it — so a call copied from any system configures a tool against the connected one. Anything a configuration cannot express (a filter that is not a conjunction of equalities, $skip, $search) is listed as not carried over rather than silently dropped. Once the description is read, the step shows how many entity sets the service publishes and whether it speaks OData V2 or V4.
Then the entity and the operation:
- Entity set. A searchable picker. The search runs on the server and matches the name or the label: every word you type must appear, in any order and case, so “order item” finds
A_SalesOrderItem. It shows the first 50 matches with “Showing 50 of N — type to narrow the list”. The chosen entity stays visible however the list is narrowed, with its property count, related-set count and key, and a draft-enabled tag when it is a draft-enabled set. - Operation. Query (many rows by filter), Read (one row by key), Create, Update (patch by key), Delete, or Action. Operations the entity set does not permit are listed as “not permitted” and cannot be chosen. A read-only entity offers no Create or Delete, and Action appears when the service declares operations bound to that entity (see below).
- Operation to run (Action only). The service’s own operations for that entity, each described by whether it reads without changing anything, whether it creates a new record, and what it returns.
- Include drafts in the results. For a query on a draft-enabled set. Off, the query returns active records only, which is what “the sales orders” means; on, it returns drafts as well.
2. Fields
One table, two answers per field. The heading follows the operation: Filters and response for a query, Key and response for a read, Payload and response for a create, Key, fields and response for an update, Key for a delete, Parameters and response for an action. The table opens on every field, with In use only to narrow it to the ones this tool uses and Show all to go back. Who provides it decides what goes out:- Set here (
bind) — you pin the value; the agent never sees the field. - Agent fills (
agent) — the field stays in the agent’s tool schema. - Not used (
off) — the field is left out entirely.
<Field>_from and <Field>_to, instead of one. On a Read or Delete only the key is sent: the other fields are disabled, with a tooltip saying they are not sent. Fields SAP will not accept for the chosen operation are shown locked, with the reason (for example sap:creatable="false" on Create). A field SAP requires must be set or exposed: the step names what is missing and Next stays disabled. A Query must leave at least one field on Agent fills, or Next stays disabled for that reason too — a query with nothing the agent can narrow on is one call with one answer, the same rows on every run whatever the agent was asked.
In response decides what comes back, for every operation but Delete. The ticks become the request’s $select, so an unticked field is never fetched rather than fetched and discarded. A level you do not touch uses the service’s own description (up to 12 identifying fields for the header, the key first, and up to 6 columns for a table), and those defaults are ticked for you so the boxes show what the tool will actually do.
Related record sets are nested inside the same table, because they are part of the same document rather than a separate list of extras. Add one with Add related data; ticking a field inside it is what puts the $expand on the request. They can nest as deep as the service allows, up to 6 navigations (the dialog offers three levels), so a sales order’s items, their schedule lines and their pricing conditions are all one configuration. On a Create an insertable set is also written inline in the same request, with its own Who provides it column, so the whole document is created or none of it. A set the service marks not insertable can still be added: it is read back after the write rather than written with it. Every create and update reads the document back afterwards, which is why a created order shows its items instead of a bare header.
Below the table, for a Query, a row of controls sets Maximum rows (default 50, at most 500), Sort by (only fields SAP marks sortable are offered, ascending or descending), Total match count, which lets the agent say “12 of 340” at the cost of an extra count on the SAP side, and on a V4 service Free-text search, which adds a search argument sent as $search. On a draft-enabled entity, Include drafts is offered there too; off, the query returns active records only.
Delete is off by default. A delete tool shows a single control, Allow this agent to delete in SAP, and cannot be saved without it (allow_delete).
Saving compiles the configuration against the service’s live $metadata: the parameters the agent will be asked for are worked out and frozen into the tool then. A configuration the service would reject is refused here, naming the field or the annotation. At run time the tool re-checks the configuration against the service’s current $metadata (cached for up to an hour), so a field removed by a later upgrade fails clearly rather than producing a bad request.
3. Result Card
The card is how a run looks in the feed: HTML in the feed renderer’s Handlebars subset, drawn by the same component the feed uses, so the preview (built from sample values: the shapes are real, the data is not) is what a run shows. The step opens on the built-in card: a template derived from the fields you ticked, in the shape of the product’s other SAP cards — a header bar with the SAP mark (or your Logo URL) and the key as a badge, a two-column Header grid of boxed values with the rest behind More, a collapsible section per related record set, and framed tables that scroll past eight rows with a per-row Details holding the columns that did not fit and whatever hangs off the row. A record set beneath a single-valued record (a customer’s sales areas under a business partner’s customer record) is drawn too. Tabs need a script, which a card may not carry, so related sets stack as collapsible sections instead. It is shown rendered and as its HTML, which is yours to edit. Until you take it over it is marked built-in · follows the fields and is rebuilt on every save, so a field ticked later appears on the card by itself. From there, three ways to change it:- Generate. Write a UI Description (“lead with the net amount and the sold-to party; show items as a table with the schedule lines under each row”) and Generate a new card from it; Regenerate runs it again. One model call made while you configure; running the tool makes none.
- Iterate. Under the preview, Provide feedback to improve (“put the status in the header bar”, “right-align the amounts”) and Iterate to have the card on screen improved. Generator warnings — a path the result will not carry — are shown above the preview.
- Edit the HTML directly in the text box; the preview follows as you type. Copy HTML copies it. Start Over or Reset to built-in returns to the built-in card.
view (one record) or views (a query’s rows), a mirror of the result with every value already formatted: dates in ISO form, an amount followed by its currency and a quantity by its unit, a code followed by its description in parentheses, booleans as Yes and No, and SAP’s all-zeros “no reference” values left blank. A V2 {"results": […]} expanded collection and a V4 plain array both arrive as a plain array, so one template serves either version. Hidden, draft-bookkeeping and protocol fields are left out, and fields keep the order the service declares them. Every value in the mirror is HTML-escaped. A template may bind only view, views, the counts (record_count, total_count), the result flags and logo_url. A template that binds the raw record, records or SAP’s message text is refused when the tool is saved, because the card would draw SAP’s text unescaped. An expression the renderer does not understand renders as nothing; a helper call, @index, ../, lookup, a script or an event handler is refused when the tool is saved.
SAP’s messages and run notices are never drawn by the template: the feed shows them around the card.
The step stores four keys on the tool: template, template_source (static for the built-in card, generated, or edited), logo_url and template_instructions (the description, kept so a regeneration after the fields change starts from the same words). Each run’s result carries the card that was in force when it ran, as ui_template and logo_url. A template may be at most 64 KB, the description at most 4 KB, and the logo must be an http or https URL; each is checked when the tool is saved. Create Tool (or Save Changes) is enabled once the configuration has compiled.
Filters (Query)
Every query field you set or expose becomes a condition, and all conditions are combined withand.
- Equality. A field set here pins
Field eq value. A field the agent fills adds the same condition when the agent supplies a value, and is simply absent when it does not. Fields SAP marks unfilterable are locked. If the entity requires a filter, at least one filter field must be set or exposed; a field SAP marks required-in-filter must itself be set or exposed. - Range. A date, timestamp or number field that the agent fills can be a range (
filter: "range"in the stored configuration). The agent is then offered<Field>_fromand<Field>_toinstead of the field itself; each is optional, and each is inclusive (geandle). “Orders since Monday” sends only_from. A range is refused at save on anything but a query, on a field that is not agent-filled, that SAP marks unfilterable, or that is not a date, timestamp or number. - Search. A V4 query can offer the agent a free-text
searchargument (search: true), sent as$search. A single word is sent as is; text containing a space or a quote is sent as a quoted phrase. Nothing is sent when the agent leaves it empty. Refused at save on a V2 service (a V2 gateway answers$searchwith 400) and on anything but a query. - Drafts. A query on a draft-enabled set pins
IsActiveEntity eq true, because the active row is what “the sales orders” means. Include drafts in the results (include_drafts) returns drafts as well.
Actions and function imports
A great deal of SAP business logic has no CRUD equivalent: setting a billing block, updating prices, withdrawing from approval. Choose Action as the operation and pick one. The tool handles the three shapes these come in:- Bound to one record — needs the key, exactly as a read does.
- Bound to the entity set — a factory, which needs no key. It creates a new document.
- Unbound — belongs to the service rather than to any entity.
- An action’s name may not be unique. One service can declare the same name bound to the header and again bound to the item. Each is configured separately; the entity you picked says which.
- Where the result comes from is the action’s return type, not the entity it is bound to. An action that returns nothing gets no card — the result is SAP’s own message.
m:HttpMethod decides whether it reads or writes. In V4 they are actions and functions: an action is posted with a JSON body, a function is fetched. The configuration is the same either way.
OData V2 and V4
What differs is only how a request is written and an answer is read, and that is handled for you.
Notes that apply to both:
- Concurrency. Updating, deleting or running a row-bound action on an entity that declares a concurrency token reads the row first and sends its real token as
If-Match, never*. Without it SAP refuses the write with428 Precondition Required. An action bound to the whole set has no row and needs no token. If the read fails, the call stops with an error that says so. - Messages. Messages are returned on success as well as failure, because a created document routinely carries a warning. The tool reads both message headers and lifts
SAP__Messagesout of every returned record, so it never shows up as a field. - Errors. When SAP’s error has per-item detail, those lines are shown in preference to the generic outer message.
Draft-enabled entities
A V4 entity set is draft-enabled whenIsActiveEntity is part of its key. Every row of such a set can exist twice, as the active record and as a draft that only its author sees, and nothing is done until the draft is activated. The tool runs the lifecycle for you; IsActiveEntity and the other draft bookkeeping fields are never offered to the agent.
- Create: the create answers with a draft. The tool then calls Prepare (when the service declares it) and Activate, and reads the active row back.
- Update: Edit the active row into a draft (with
PreserveChanges=truewhen the Edit action takes that parameter, and withIf-Matchon a guarded entity), PATCH the draft, Prepare when declared, Activate, then read the active row back. - On failure: if Prepare, Activate or the change to the draft fails, the tool calls Discard and reports
NN/DRAFT_NOT_ACTIVATEDas an error message. If Edit itself fails there is no draft to discard. Either way the call returns an error, withdraftand SAP’s messages on the result. - Activate with no answer: if Activate gets no answer, or a 502, 503 or 504, the tool checks before discarding anything. If the draft is gone and the active row is there, the activation went through and the result says so with
NN/DRAFT_ACTIVATE_UNANSWERED. - Draft nodes are refused. An item under a draft root has no lifecycle of its own and is created, changed and activated only through its root, so a Create, Update or Delete configured on a node is rejected when you save it.
- Activation is marked unverified. The create-as-draft and Discard exchanges have been observed against a live service. The Prepare, Edit and Activate exchanges follow SAP’s published draft contract and have not, so every draft result carries
draft.unverified: true. Checkdraft.activatedand the record read back before relying on the outcome.
Inputs
The agent-visible parameters depend on the configured entity and operation — they are the fields you left as Agent fills, named exactly as SAP names them, with the service’s own labels as their descriptions, SAP’s own field lengths, and dates typed as dates. A field pinned with Set here or switched off is not in the agent’s parameters at all, and a query filter SAP marks unfilterable is left out rather than offered and rejected.Outputs
Keeping large reads out of the agent’s context
On by default, for Query and Read only: a successful read stores its structured result as a task variable, so the agent hands the rows to another tool as${VAR_N} instead of carrying them in every later turn. On an agent with harness_v2, a result over 1 KB is then collapsed to that reference plus a short preview, which is where the context saving comes from — a query that returned two hundred rows stops being re-sent on every decision.
The card keys (ui_template, logo_url, view, views, card) never reach the model at all; they are feed-only, and the variable is stored without them. The full result always stays on the step, whatever this is set to.
Writes — Create, Update, Delete and Actions — register no variable: their answer is the record just written, which is small, and what matters about it is that it happened.
To keep reads inline only, bind store_result_as_variable to false on a configured tool. The binding is hidden from the agent, and any value other than true or false is rejected when saved. Set it through the API or the platform MCP’s update_configured_tool; the configured-tool dialog keeps it when the tool is saved again.
Alongside that structured result, every call returns a short markdown summary — the record’s header fields, its related record sets as tables, and any SAP message. It follows the card layout derived from the service’s metadata. This is what the task summary is written from. Rows are capped (20 for a query, 25 per related set, cells cut at 60 characters) and whatever is left out is counted, never dropped silently. When SAP reported an error on an accepted request, the summary says to treat the operation as not done.
Limits
- Rows. A query returns the Maximum rows you set, default 50, ceiling 500.
- Paging. Server-driven paging is followed until the row limit is met, at most 20 pages. After the first page, paging stops once 2 minutes have been spent; the rows already read are returned with
truncated: trueand apartial_reasonof the formstopped after N page(s): the 2m0s budget for one call was spent. The first page is never subject to the budget. A next-link is used exactly as SAP sent it and must point at the connection’s own host. - Retries. Each request has a 60-second timeout and is retried up to 3 times on a network error or an HTTP 429, 500, 502, 503 or 504, with exponential backoff (roughly 2, 4 then 8 seconds, with jitter, never more than 10). Writes are included.
- Session token. Every write first establishes a fresh CSRF token by reading
$metadata, so a token is never carried over from an earlier call. A write refused with 403 andx-csrf-token: Requiredis retried once with a freshly fetched token. - Depth. Related records follow up to 6 navigations. Deeper is refused when you save.
- One operation per tool. Configure two tools if you need to read and create.
- Filters. Conditions combine with
and, on equality, plus the inclusivege/lebounds of a range. There is noor, no other comparison, no function and no$skip. This is deliberate: classic SAP gateway services flatten a filter into ABAP select-options, which cannot expressor, and several implementations respond by returning the unfiltered set. - Related records on create are limited to what the service marks insertable, which is not always the same as what its entity sets allow.
- Imported calls carry
$filteronly where it is equality clauses joined byand. Anor, a comparison or a function is reported, not partially applied. - Messages. Each message header value over 64 KB is skipped, and at most 200 messages are kept per response; when that drops anything, an
NN/TRUNCATEDinfo message says so. - Key padding. A not-found on a Read, Update or Delete whose key is all digits and shorter than the field is retried once with the key zero-padded; the result then carries
key_padded: true. - Metadata cache. A running worker re-reads a service’s
$metadataat most once an hour (a failed read is remembered for 30 seconds). The cache is keyed on the same connection identity as the catalog, so a service one connection may not read is never answered from another’s copy.
Side effects
- Query and Read are read-only. So is an Action whose function import or V4 function is fetched with GET.
- Create, Update and Delete, and any Action that is posted, write to the connected SAP system and are not reversible from here. Pair write operations with an approval step for anything running against production.
- Extra reads around a write. A write reads
$metadatafor its session token; an Update, Delete or row-bound Action on a guarded entity reads the row first for its token; and every Create and Update reads the document back afterwards. - A write can be sent more than once. The retry layer re-sends a write that meets a network error or a 429 or 5xx answer, and SAP may already have processed the first attempt. If a create reports a failure, look for the document before repeating it.
- Drafts. A draft-enabled write can leave a draft behind. Whenever activation does not happen — it failed, or the service declares no Activate action at all — the tool tries to discard the draft;
draft.discardedsays whether that worked. Only if the discard itself fails does the draft stay open in SAP, where it can hold a lock on the row until someone activates or discards it there. - Designing the card. Designing or refining the result card in the setup flow makes a model call at configuration time. Running the tool makes none.
- Logging. Each call records a trace entry with the service, entity, operation and counts only, never field values or URLs, and every request to SAP is audit-logged (host, method and status only, never the path or query).
Errors
A call can fail in three places: the tool refuses before calling SAP, SAP answers with an error, or SAP accepts the request but reports a problem inmessages.
Messages inside a result (the call itself did not fail):
Rejected when you save the tool: