SapGui.Wrapper.Mcp
MCP stdio server for SapGui.Wrapper β exposes SAP GUI scripting as 18 Model Context Protocol tools consumable by Claude Desktop, VS Code Copilot, Cursor, and any other MCP-compatible host.
Ask AI about SapGui.Wrapper.Mcp
Powered by Claude Β· Grounded in docs
I know everything about SapGui.Wrapper.Mcp. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
SapGui.Wrapper

A strongly-typed .NET wrapper for the SAP GUI Scripting API Purpose-built for UiPath Coded Workflows - giving RPA developers IntelliSense, compile-time safety, and clean, readable code instead of raw COM calls.
Why this wrapper?
Native SAP GUI automation in UiPath forces you to choose between:
| Approach | Problem |
|---|---|
| Classic Activities (click/type) | Fragile, image-based, slow |
Invoke Code + raw COM (SapROTWr) | Verbose, no IntelliSense, runtime errors only |
| Script Recorder β VBScript | Not reusable, no structure |
SapGui.Wrapper gives you another option: paste the recorder IDs you already have, use C# or VB.NET objects with full IntelliSense, and get exceptions with meaningful messages the moment anything goes wrong.
UiPath Coded Workflow (C# / VB.NET)
β
βΌ
SapGui.Wrapper β this NuGet package
β
βΌ
sapfewse.ocx β SAP GUI Scripting Engine (installed with SAP GUI)
β
βΌ
SAP GUI (running)
Zero build-time dependency on the OCX - the package uses late-binding COM interop, so it works on any machine where SAP GUI is installed.
Prerequisites
| Requirement | Details |
|---|---|
| SAP GUI | 7.40 or later, installed on the robot machine |
| Scripting enabled | SAP GUI Options β Accessibility & Scripting β Enable scripting |
| .NET | net461 (legacy UiPath) or net6.0-windows (UiPath Studio 23+) |
| Platform | x64 Windows only (SAP GUI constraint) |
Installation
In UiPath Studio:
Manage Packages β NuGet.org β search SapGui.Wrapper β Install
CLI:
dotnet add package SapGui.Wrapper
UiPath β Coded Workflow
This is the primary use case. A CodedWorkflow file in UiPath Studio is a plain C# class - use it exactly like any other C# code.
using System.Data;
using SapGui.Wrapper;
using UiPath.CodedWorkflows;
namespace MyUiPathProject
{
public class ProcessMaterialList : CodedWorkflow
{
[Workflow]
public DataTable Execute(string plant)
{
using var sap = SapGuiClient.Attach();
var session = sap.Session;
// ββ Log who we're running as ββββββββββββββββββββββββββββββββββββββββββ
Log($"System: {session.Info.SystemName} | User: {session.Info.User} | TCode: {session.Info.Transaction}");
// ββ Navigate ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
session.MainWindow().Maximize();
session.StartTransaction("MM60");
// ββ Fill selection screen βββββββββββββββββββββββββββββββββββββββββββββ
session.TextField("wnd[0]/usr/txtS_WERKS-LOW").Text = plant;
session.PressExecute(); // F8
// ββ Handle "no results" popup βββββββββββββββββββββββββββββββββββββββββ
var popup = session.GetActivePopup();
if (popup != null)
{
Log($"Popup: {popup.Text}", LogLevel.Warn);
popup.ClickOk();
return new DataTable();
}
// ββ Read ALV grid βββββββββββββββββββββββββββββββββββββββββββββββββββββ
var grid = session.GridView("wnd[0]/usr/cntlGRID/shellcont/shell");
var rows = grid.GetRows(new[] { "MATNR", "MAKTX", "MEINS", "LABST" });
// ββ Convert to DataTable for UiPath data activities βββββββββββββββββββ
var dt = new DataTable();
dt.Columns.Add("Material"); dt.Columns.Add("Description");
dt.Columns.Add("Unit"); dt.Columns.Add("UnrestrictedStock");
foreach (var row in rows)
dt.Rows.Add(row["MATNR"], row["MAKTX"], row["MEINS"], row["LABST"]);
session.ExitTransaction();
return dt;
}
}
}
What makes this clean for RPA
StartTransactionreplaces typing/nXX+ Enter every timeGetActivePopup()handles bothGuiMessageWindowandGuiModalWindowuniformlygrid.GetRows(columns)returnsList<Dictionary<string, string>>- maps directly to DataTable columnsExitTransaction()navigates back to Easy Access without hardcoding/n- Every method throws a descriptive exception on failure - no silent
nullreturns from COM
UiPath β Invoke Code activity (VB.NET)
Works in classic UiPath projects without Coded Workflows:
Dim sap As SapGuiClient = SapGuiClient.Attach()
Dim session As GuiSession = sap.Session
session.StartTransaction("VA01")
session.TextField("wnd[0]/usr/ctxtVBAK-AUART").Text = "OR"
session.ComboBox("wnd[0]/usr/cmbVBAK-VKORG").Key = "1000"
session.PressEnter()
Dim status As String = session.Statusbar().Text
Dim isError As Boolean = session.Statusbar().IsError
UiPath β static one-liners (SapGuiHelper)
For the simplest workflows where you just need to set a field or press a button, SapGuiHelper removes even the Attach() call:
' VB.NET in UiPath Invoke Code - no variable management needed
SapGuiHelper.StartTransaction("MM01")
SapGuiHelper.SetText("wnd[0]/usr/txtMM01-MATNR", "MAT-0042")
SapGuiHelper.PressEnter()
Dim msg As String = SapGuiHelper.GetStatusMessage()
If SapGuiHelper.HasError() Then Throw New Exception("SAP error: " & msg)
Getting component IDs from the SAP Script Recorder
Every component ID ("wnd[0]/usr/txtRSYST-BNAME") comes directly from the SAP GUI Script Recorder:
- In SAP GUI: Alt+F12 β Script Recording and Playback β Start Recording
- Perform the actions manually
- Stop recording - the VBScript output contains every ID you need
- Paste those IDs into this wrapper; only change is adding
()to method calls
Recorder output:
session.findById("wnd[0]/usr/txtRSYST-BNAME").text = "MYUSER"
session.findById("wnd[0]/tbar[1]/btn[8]").press
C# equivalent - paste the IDs, one mechanical change:
session.findById("wnd[0]/usr/txtRSYST-BNAME").Text = "MYUSER";
session.findById("wnd[0]/tbar[1]/btn[8]").press();
findById returns dynamic, so all SAP properties and methods resolve at runtime - identical to VBScript. For compile-time IntelliSense, use the typed overload:
session.FindById<GuiTextField>("wnd[0]/usr/txtRSYST-BNAME").Text = "MYUSER";
Common patterns
Reading a classic ABAP table
var table = session.Table("wnd[0]/usr/tblSAPLXXX");
// Scroll-aware read: loop in visible-row increments
int totalRows = table.RowCount;
int pageSize = table.VisibleRowCount;
for (int start = 0; start < totalRows; start += pageSize)
{
table.ScrollToRow(start);
for (int r = start; r < Math.Min(start + pageSize, totalRows); r++)
Console.WriteLine(table.GetCellValue(r, 0));
}
Reading an ALV grid (scroll-aware)
var grid = session.GridView("wnd[0]/usr/cntlGRID/shellcont/shell");
Log($"Total rows: {grid.RowCount}, visible: {grid.VisibleRowCount}, first: {grid.FirstVisibleRow}");
// Navigate to a specific cell before reading a tooltip or checkbox
grid.SetCurrentCell(3, "STATUS");
string tooltip = grid.GetCellTooltip(3, "STATUS");
bool flagged = grid.GetCellCheckBoxValue(3, "CRITICAL");
// Get currently selected rows (e.g. after SelectAll)
grid.SelectAll();
var selected = grid.SelectedRows; // IReadOnlyList<int>
Handling popups
// Works for both GuiMessageWindow (pure dialogs) and GuiModalWindow (form dialogs)
var popup = session.GetActivePopup();
if (popup != null)
{
Log($"[{popup.MessageType}] {popup.Title}: {popup.Text}");
// Option 1 β standard buttons
popup.ClickOk(); // or popup.ClickCancel()
// Option 2 β custom button by partial text
popup.ClickButton("Continue");
// Option 3 β inspect all buttons
foreach (var btn in popup.GetButtons())
Log(btn.Text);
}
Tabs, toolbars, and menus
// Select a tab
session.TabStrip("wnd[0]/usr/tabsTABSTRIP").SelectTab(1);
// Press a toolbar button by SAP function code
session.Toolbar().PressButtonByFunctionCode("BACK");
// Navigate a menu
session.Menu("wnd[0]/mbar/menu[4]/menu[2]").Select();
Tree controls
var tree = session.Tree("wnd[0]/usr/cntlTREE/shellcont/shell");
tree.ExpandNode("ROOT");
foreach (var key in tree.GetChildNodeKeys("ROOT"))
Log($"{key}: {tree.GetNodeText(key)}");
tree.SelectNode("CHILD01");
tree.DoubleClickNode("CHILD01");
Multi-session workflows
var sap = SapGuiClient.Attach();
// Get existing sessions
var session0 = sap.GetSession(connectionIndex: 0, sessionIndex: 0);
var session1 = sap.GetSession(connectionIndex: 0, sessionIndex: 1);
// Open a new parallel session on demand
session0.CreateSession();
// Then retrieve it:
var newSession = sap.Application
.GetFirstConnection()
.GetSessions()
.Last();
Reactive automation with session events
StartMonitoring() starts a lightweight background thread that raises .NET events when the session state changes - useful for logging round-trips or detecting abends without polling in your workflow loop.
var session = SapGuiClient.Attach().Session;
session.StartRequest += (_, e) =>
Log($"Round-trip starting");
session.EndRequest += (_, e) =>
Log($"Round-trip done - FunctionCode={e.FunctionCode}");
session.Change += (_, e) =>
Log($"Round-trip done - [{e.MessageType}] {e.Text} FunctionCode={e.FunctionCode}");
session.AbapRuntimeError += (_, e) =>
throw new Exception($"ABAP abend: {e.Message}");
session.Destroy += (_, _) =>
Log("SAP session closed", LogLevel.Warn);
// StartMonitoring tries a COM event sink first (no polling thread).
// Falls back to polling (500 ms) if the COM sink cannot connect.
session.StartMonitoring(pollMs: 500);
// ... run your automation ...
session.StopMonitoring();
Connect to a new SAP system
var app = GuiApplication.Attach();
var connection = app.OpenConnection("PRD - Production"); // SAP Logon Pad entry name
var session = connection.GetFirstSession();
// Or quickly get whichever session has focus
var active = app.ActiveSession;
SSO / Unattended login
Use LaunchWithSso when the robot must start SAP itself (e.g. unattended UiPath jobs).
It starts saplogon.exe if it isn't already running, opens the connection via SSO
(no credential dialog), and blocks until the session is ready.
// SapGuiClient and GuiSession both implement IDisposable -
// their COM references are released deterministically when the block exits.
using var sap = SapGuiClient.LaunchWithSso("PRD - Production");
// sap.Session creates a new GuiSession wrapper each time it is called.
// Assign it to a local variable (with 'using' if you want deterministic COM release).
// Disposing 'session' releases only the .NET RCW - the SAP session itself
// remains open inside SAP GUI. Calling sap.Session again returns a fresh
// wrapper for the same live SAP session.
using var session = sap.Session;
// Clear any post-login popups (system messages, license notices, etc.)
int dismissed = session.DismissPostLoginPopups();
// Now automate normally
session.StartTransaction("MM60");
Existing session already open
When the same user is already logged on to the same SAP system, SAP Logon shows a
"License information for multiple logons" dialog that is owned by saplogon.exe
itself β outside the SAP scripting API β and blocks the new connection from completing.
Use the reuseExistingSession parameter to control this:
// true β skip opening a new connection; return a client wrapping the existing session
using var sap = SapGuiClient.LaunchWithSso("PRD - Production", reuseExistingSession: true);
// false (default) β throw InvalidOperationException if a session is already open,
// so the caller can decide what to do (attach, close, or abort)
using var sap = SapGuiClient.LaunchWithSso("PRD - Production"); // throws if already logged on
DismissPostLoginPopups handles (in order):
- Multiple Logon / User already logged on β clicks Continue (keeps both sessions alive)
- License expiration warnings β clicks OK
- System message / Message of the Day banners β clicks OK
- Any single-button info dialog β presses that button
- Unrecognised multi-button dialogs are left untouched
Waiting for SAP to finish
After navigation steps that trigger a server round-trip, call WaitReady() to block until SAP is done:
session.PressExecute();
session.WaitReady(timeoutMs: 30_000); // default: 30 seconds, polls every 200 ms
if (session.Statusbar().IsError)
throw new Exception($"SAP error: {session.Statusbar().Text}");
Resilient automation: retry and waiting
SAP GUI is timing-sensitive. Network latency, slow ABAP reports, and post-navigation busy pulses all cause flaky automation when you interact with the screen too early. The wrapper provides five methods to handle this:
| Method | Use when |
|---|---|
WaitReady(timeoutMs) | Simple busy-flag poll after navigation |
WaitForReadyState(timeoutMs, pollMs, settleMs) | Stricter: also waits for a settle period and verifies the main window is reachable |
ElementExists(id, timeoutMs) | Poll until a specific component appears before touching it |
WaitUntilHidden(id, timeoutMs) | Poll until a loading spinner or progress dialog disappears |
WithRetry(maxAttempts, delayMs).Run(β¦) | Re-execute a block if it raises SapComponentNotFoundException or TimeoutException |
WaitForReadyState β stricter wait
Use WaitForReadyState instead of WaitReady after screen transitions that trigger a brief second busy pulse:
session.StartTransaction("/nMM60");
// Verifies IsBusy is false, adds a 300 ms settle, then confirms the main window is reachable.
session.WaitForReadyState(timeoutMs: 15_000, settleMs: 300);
ElementExists β wait for a component to appear
// Don't call TextField() until the field is actually on screen.
const string reportField = "wnd[0]/usr/ctxtSELWERKS-LOW";
if (!session.ElementExists(reportField, timeoutMs: 8_000))
throw new Exception("Selection screen did not load in time.");
session.TextField(reportField).Text = plant;
WaitUntilHidden β wait for a spinner or dialog to close
// Wait out a 'Please waitβ¦' processing dialog before reading the result.
const string spinner = "wnd[1]/usr/txtMESSAGE";
session.PressExecute();
session.WaitUntilHidden(spinner, timeoutMs: 60_000);
session.WaitForReadyState(timeoutMs: 10_000);
WithRetry β resilient execution block
WithRetry retries on SapComponentNotFoundException (slow screen loads) and TimeoutException (session still busy). It never retries on SapGuiNotFoundException - that is a fatal setup error and should propagate immediately.
// Wrap any navigation + field-access block that may race on slow networks.
session.WithRetry(maxAttempts: 3, delayMs: 500).Run(() =>
{
session.StartTransaction("/nMM60");
session.WaitForReadyState(timeoutMs: 15_000);
session.TextField("wnd[0]/usr/ctxtSELWERKS-LOW").Text = plant;
session.PressExecute();
session.WaitForReadyState(timeoutMs: 30_000);
});
Return a value from the protected block with Run<T>:
string status = session.WithRetry(maxAttempts: 3, delayMs: 400).Run(() =>
{
session.WaitReady(timeoutMs: 10_000);
return session.Statusbar().Text;
});
Combined pattern - navigate, wait, read with retry
using var sap = SapGuiClient.Attach();
var session = sap.Session;
const string tableField = "wnd[0]/usr/ctxtDATABROWSE-TABLENAME";
// 1. Navigate with retry in case the first attempt races
session.WithRetry(maxAttempts: 3, delayMs: 400).Run(() =>
{
session.StartTransaction("/nSE16");
session.WaitForReadyState(timeoutMs: 10_000);
// Confirm the expected field is present before continuing
if (!session.ElementExists(tableField, timeoutMs: 5_000))
throw new SapComponentNotFoundException(tableField);
});
// 2. Interact only after the page is confirmed ready
session.TextField(tableField).Text = "MARA";
session.PressExecute();
session.WaitForReadyState(timeoutMs: 30_000);
// 3. Wait for a progress dialog to disappear if one appears
bool hadSpinner = session.WaitUntilHidden("wnd[1]", timeoutMs: 60_000);
if (hadSpinner) Log("Progress dialog closed.");
var grid = session.GridView("wnd[0]/usr/cntlGRID/shellcont/shell");
Log($"Rows returned: {grid.RowCount}");
Logging
The wrapper is silent by default - zero overhead when no logger is configured.
Three overloads of Attach and LaunchWithSso accept a logger:
| Overload | When to use |
|---|---|
Attach() / LaunchWithSso(system) | No logging needed |
Attach(ILogger) / LaunchWithSso(system, ILogger) | ASP.NET Core DI, Serilog/NLog via MEL adapter |
Attach(SapLogAction) / LaunchWithSso(system, SapLogAction) | UiPath Log(), Serilog static Log, console β no MEL dependency |
Log levels emitted
| Level | Events |
|---|---|
| Debug | Every FindById / FindByIdDynamic call (path logged) |
| Information | StartTransaction, ExitTransaction, session open/close, LaunchWithSso progress, popup dismissed |
| Warning | Popup detected, retry attempt, WaitReady/WaitForReadyState near timeout |
| Error | All thrown exceptions before they propagate |
UiPath β route SAP logs to UiPath's Log() action
No NuGet dependency on Microsoft.Extensions.Logging needed in your workflow:
// All levels β each SAP level maps to the matching UiPath LogLevel
using var sap = SapGuiClient.Attach(
logAction: (level, msg, ex) => Log(
$"[SAP/{level}] {msg}",
level switch
{
SapLogLevel.Error => LogLevel.Error,
SapLogLevel.Warning => LogLevel.Warn,
SapLogLevel.Debug => LogLevel.Trace,
_ => LogLevel.Info,
}));
// Warning and Error only β less noise in production runs
using var sap = SapGuiClient.Attach(
logAction: (level, msg, ex) => Log($"[SAP/{level}] {msg}"),
minLevel: SapLogLevel.Warning);
// With LaunchWithSso (unattended jobs):
using var sap = SapGuiClient.LaunchWithSso(
"PRD - Production",
reuseExistingSession: true,
logAction: (level, msg, ex) => Log(
$"[SAP/{level}] {msg}",
level switch
{
SapLogLevel.Error => LogLevel.Error,
SapLogLevel.Warning => LogLevel.Warn,
SapLogLevel.Debug => LogLevel.Trace,
_ => LogLevel.Info,
}),
minLevel: SapLogLevel.Warning);
ASP.NET Core / Serilog / NLog (via MEL adapter)
// ILogger injected by DI, or created via LoggerFactory
using var sap = SapGuiClient.Attach(logger);
// Or with LaunchWithSso:
using var sap = SapGuiClient.LaunchWithSso("PRD - Production", logger);
Serilog static Log class (no MEL adapter)
using var sap = SapGuiClient.Attach(logAction: (level, msg, ex) =>
Serilog.Log.Write(level switch
{
SapLogLevel.Debug => Serilog.Events.LogEventLevel.Debug,
SapLogLevel.Warning => Serilog.Events.LogEventLevel.Warning,
SapLogLevel.Error => Serilog.Events.LogEventLevel.Error,
_ => Serilog.Events.LogEventLevel.Information,
}, ex, "{SapMessage}", msg));
Console (quick debugging)
using var sap = SapGuiClient.Attach(
logAction: (level, msg, ex) =>
Console.WriteLine($"[{level}] {msg}{(ex is null ? "" : " β " + ex.Message)}"));
Pre-flight health check
Before running automation in a robot, call HealthCheck() to produce a structured report - or EnsureHealthy() for a single fail-fast call.
// Non-throwing: inspect findings yourself
var result = SapGuiClient.HealthCheck();
if (!result.IsHealthy)
throw new InvalidOperationException(result.FailureSummary);
foreach (var line in result.Findings)
Log(line); // each line is prefixed OK: / WARN: / FAIL:
// Throwing shorthand - equivalent to the above
SapGuiClient.EnsureHealthy();
// Then proceed normally
using var sap = SapGuiClient.Attach();
sap.Session.StartTransaction("SE16");
Checks performed (in order):
| # | Check | FAIL condition |
|---|---|---|
| 1 | saplogon.exe is running | Process not found - SAP GUI not installed / not started |
| 2 | Scripting API accessible via Windows ROT | Scripting disabled or ROT registration failed |
| 3 | At least one active connection | No SAP system logged on |
| 4 | At least one active session | Connection exists but no window open |
| 5 | Session info readable (user / system / client) | Session is mid-logon or in an error state |
Exception types
| Exception | When thrown |
|---|---|
SapGuiNotFoundException | SAP GUI is not running or scripting is disabled |
SapComponentNotFoundException | FindById path does not match any component |
InvalidCastException | FindById<T> found a component but it's a different type |
InvalidOperationException | LaunchWithSso could not open an SSO connection, or a session for the system is already open and reuseExistingSession is false |
TimeoutException | WaitReady timed out while session was busy, or LaunchWithSso found no ready session in time |
VKey reference
| VKey | Key | Action |
|---|---|---|
| 0 | Enter | Confirm / navigate forward |
| 3 | F3 | Back - one screen back within the transaction |
| 4 | F4 | Input Help / Possible Values |
| 8 | F8 | Execute - runs the current report / selection screen |
| 11 | Ctrl+S | Save |
| 12 | F12 | Cancel - discards changes and closes the current screen |
| 15 | Shift+F3 | Exit - steps back to the previous menu level |
| 71 | Ctrl+Home | Scroll to top |
| 82 | Ctrl+End | Scroll to bottom |
Shortcuts: PressEnter() Β· PressBack() Β· PressExecute() Β· ExitTransaction()
API coverage
Component wrappers
| SAP COM Class | Wrapper | Key members |
|---|---|---|
SapGuiClient | β | Attach(), Attach(logAction:, minLevel:, logger:), LaunchWithSso(system, reuseExistingSession, β¦, logAction:, minLevel:, logger:), HealthCheck(), EnsureHealthy(), Session, GetSession(), GetConnections(), Dispose() |
GuiApplication | β | Version, ActiveSession, GetConnections(), OpenConnection() |
GuiConnection | β | Description, GetSessions() |
GuiSession | β | ActiveWindow, FindById, StartTransaction, ExitTransaction, CreateSession, PressEnter/Back/Execute, SendVKey, GetActivePopup, WaitReady, WaitForReadyState, ElementExists, WaitUntilHidden, WithRetry, DismissPostLoginPopups, StartMonitoring (COM sink β polling fallback), StartRequest, EndRequest, Change, Destroy, AbapRuntimeError, UserArea, ScrollContainer, Calendar, HtmlViewer, Shell, Dispose |
GuiMainWindow | β | Title, IsMaximized, SendVKey, HardCopy, Maximize, Iconify, Restore, Close |
GuiTextField | β | Text, DisplayedText, MaxLength, IsReadOnly, IsRequired, IsOField, CaretPosition |
GuiPasswordField | β * | Text set (write-only) - falls back to GuiTextField |
GuiButton | β | Text, Press() |
GuiLabel | β | Text (read-only) |
GuiCheckBox | β | Selected |
GuiRadioButton | β | Selected |
GuiComboBox | β | Key, Value, ShowKey, Entries |
GuiStatusbar | β | Text, MessageType, IsError / IsWarning / IsSuccess |
GuiTable | β | RowCount, ColumnCount, FirstVisibleRow, VisibleRowCount, ScrollToRow, CurrentCellRow/Column, GetCellValue, SetCellValue, GetVisibleRows, SelectRow |
GuiGridView | β | RowCount, FirstVisibleRow, VisibleRowCount, ColumnNames, CurrentCellRow/Column, SetCurrentCell, SelectedRows, GetCellValue, GetCellTooltip, GetCellCheckBoxValue, GetSymbolsForCell, GetRows, PressEnter, ClickCell, PressToolbarButton, SelectAll |
GuiTree | β | ExpandNode, CollapseNode, SelectNode, DoubleClickNode, GetNodeText, GetItemText, GetNodeType, GetChildNodeKeys, GetAllNodeKeys, NodeContextMenu, SelectedNode |
GuiTabStrip / GuiTab | β | TabCount, GetTabs(), SelectTab(int), GetTabByName(), Tab.Select() |
GuiToolbar | β | ButtonCount, PressButton(int), PressButtonByFunctionCode(string), GetButtonTooltip(int) |
GuiMenubar / GuiMenu | β | Count, SelectItem(GuiSession, string), GetChildren() |
GuiContextMenu | β | SelectByFunctionCode(), GetItemTexts(), Close() |
GuiMessageWindow | β | Text, Title, MessageType, ClickOk(), ClickCancel(), ClickButton(), GetButtons() |
GuiModalWindow | πΆ | Via session.GetActivePopup() - children accessible with findById |
GuiUserArea | β | FindById(relativeId), FindById<T>(relativeId) - address children with relative IDs |
GuiScrollContainer | β | VerticalScrollbar, HorizontalScrollbar (Position, Minimum, Maximum, PageSize), ScrollToTop() |
GuiShell (generic) | β | SubType - typed fallback for unrecognised shell variants |
GuiCalendar | β | FocusedDate, SetDate(DateTime), GetSelectedDate() |
GuiHTMLViewer | β | BrowserHandle, FireSapEvent(event, param1, param2) |
Legend: β typed wrapper Β· πΆ accessible via dynamic / base class Β· β not yet implemented
Events
GuiSession exposes five .NET events, activated by calling StartMonitoring():
| Event | When it fires |
|---|---|
StartRequest | At the start of a server round-trip (session transitions to busy) |
EndRequest | At the end of a server round-trip, before Change (includes FunctionCode when COM sink is active) |
Change | After every server round-trip (IsBusy β idle transition) |
Destroy | When the session becomes unreachable (window closed / connection lost) |
AbapRuntimeError | When a status bar message type A (abend) is detected after a round-trip |
Since v0.8.0,
StartMonitoring()connects a true COM event sink when the SAP version supports it, with automatic fallback to polling. No extra configuration needed.
AI Agent & MCP Server
SapGui.Wrapper.Mcp is a dotnet tool that exposes SAP GUI scripting as 18 MCP tools consumable by any MCP-capable host (Claude Desktop, VS Code Copilot Agent, Cursor, etc.). It ships with a built-in screen observation layer and semantic action faΓ§ade β no separate package needed.
Install
# Global dotnet tool β resolves to sapgui-mcp on PATH
dotnet tool install --global SapGui.Wrapper.Mcp
# Or as a local project tool (dotnet tool restore will activate it)
dotnet tool install SapGui.Wrapper.Mcp
Claude Desktop
Add to claude_desktop_config.json (%APPDATA%\Claude\claude_desktop_config.json on Windows):
{
"mcpServers": {
"sapgui": {
"command": "dotnet",
"args": ["tool", "run", "sapgui-mcp", "--", "--connection", "COR\\PRD\\100"]
}
}
}
The --connection argument format is <SapLogonPadEntry>\\<SystemId>\\<Client>.
Omit it to start the server without a session; the agent can then call sap_connect to connect at runtime.
VS Code Copilot (Agent Mode)
Create .vscode/mcp.json in your workspace:
{
"inputs": [
{
"id": "sap-connection",
"type": "promptString",
"description": "SAP connection string (e.g. COR\\PRD\\100)",
"default": "COR\\PRD\\100"
}
],
"servers": {
"sapgui": {
"type": "stdio",
"command": "dotnet",
"args": ["tool", "run", "sapgui-mcp", "--", "--connection", "${input:sap-connection}"]
}
}
}
Available tools (18)
| Tool | Description |
|---|---|
sap_connect | Connect to a running SAP session by connection string and client |
sap_disconnect | Release the session |
sap_scan_screen | Full JSON snapshot of the current screen (fields, buttons, grids, tabs, trees, statusbar) |
sap_take_screenshot | Base-64 PNG of the current screen |
sap_set_field | Set a field value by label or component ID |
sap_get_field | Read a field value by label or component ID |
sap_clear_field | Clear a field by label or component ID |
sap_click_button | Click a button by text, tooltip, or SAP function code |
sap_press_key | Send a VKey (Enter, F3, F8, Ctrl+S, β¦) |
sap_start_transaction | Navigate to a transaction code (subject to guardrails) |
sap_select_menu | Click a menu item by path |
sap_select_tab | Select a tab by index or label |
sap_read_grid | Read all rows from an ALV grid |
sap_select_grid_row | Select a row in an ALV grid by index |
sap_open_grid_row | Double-click a grid row to open its detail screen |
sap_handle_popup | Dismiss or click a button in a popup dialog |
sap_expand_tree_node | Expand a tree node by key |
sap_select_tree_node | Select a tree node by key |
sap_wait_and_scan | Wait for SAP to become ready then return a fresh screen snapshot |
Security: read-only mode and blocked transactions
The server ships with 12 dangerous transaction codes blocked by default (SE38, SE37, SM49, RZ10, SCC4, SU01, PFCG, SCC5, SCC1, SM59, SM50, RZ04).
Override via appsettings.json placed next to the tool binary, or via environment variables prefixed SAP__:
{
"Sap": {
"ReadOnlyMode": true,
"BlockedTransactions": ["SE38", "SE37", "SM49", "RZ10"]
}
}
ReadOnlyMode: true restricts to observation-only tools (sap_scan_screen, sap_take_screenshot, sap_read_grid, sap_wait_and_scan). All write/navigation tools are rejected with a clear error message that the agent sees. The BlockedTransactions list is enforced regardless of ReadOnlyMode.
Building the NuGet package
cd SapGui.Wrapper
dotnet pack -c Release -o ../nupkg
After packing, an SBOM (SapGui.Wrapper-{version}-sbom.cdx.json) is generated automatically alongside the .nupkg via dotnet CycloneDX. Restore the tool once if it is not yet installed:
dotnet tool restore # reads .config/dotnet-tools.json
To sign the packages with a self-signed certificate:
.\scripts\New-SigningCert.ps1
# follow the on-screen instructions and record the printed thumbprint
Verify a signed package:
dotnet nuget verify nupkg\SapGui.Wrapper.1.0.0.nupkg --certificate-fingerprint <thumbprint>
Project structure
SapGui.Wrapper/
βββ SapGuiClient.cs β main entry point (Attach / LaunchWithSso / HealthCheck / GetSession)
βββ SapGuiHelper.cs β static one-liner helpers for UiPath Invoke Code
βββ SapLogging.cs β SapLogLevel enum, SapLogAction delegate, SapLogger bridge
βββ HealthCheckResult.cs β HealthCheckResult record (IsHealthy, Findings, FailureSummary)
βββ Exceptions.cs
βββ GlobalUsings.cs
βββ Polyfills.cs β IsExternalInit shim for net461 record support
β
βββ Com/
β βββ SapRot.cs β ROT access (SapROTWr + P/Invoke fallback)
β βββ ComConnectionPoint.cs β IConnectionPoint / IConnectionPointContainer COM interfaces
β βββ GuiSessionComSink.cs β COM-visible dispatch sink for SAP session events
β
βββ RetryPolicy.cs β configurable retry (WithRetry, Run, Run<T>)
β
βββ Core/
β βββ GuiComponent.cs β base wrapper (late-binding helpers)
β βββ GuiApplication.cs β GetConnections, OpenConnection, ActiveSession
β βββ GuiConnection.cs β GetSessions
β βββ GuiSession.cs β FindById, typed finders, events, WaitForReadyState, ElementExists, WaitUntilHidden, WithRetry, DismissPostLoginPopups, Dispose
β βββ GuiMainWindow.cs β SendVKey, HardCopy, Maximize
β βββ SessionEvents.cs β SessionChangeEventArgs, SessionEventMonitor
β
βββ Elements/
β βββ GuiTextField.cs
β βββ GuiButton.cs
β βββ GuiComboBox.cs
β βββ GuiCheckBox.cs / GuiRadioButton.cs
β βββ GuiLabel.cs
β βββ GuiStatusbar.cs
β βββ GuiTable.cs β FirstVisibleRow, ScrollToRow, CurrentCell
β βββ GuiGridView.cs β SetCurrentCell, SelectedRows, GetCellTooltip, β¦
β βββ GuiTree.cs
β βββ GuiTabStrip.cs
β βββ GuiToolbar.cs
β βββ GuiMenu.cs β GuiMenubar, GuiMenu, GuiContextMenu
β βββ GuiMessageWindow.cs
β
βββ Enums/
βββ SapComponentType.cs
License
MIT
