TIA Portal MCP server
MCP server for Siemens SIMATIC TIA Portal V21. It lets MCP clients and AI agents inspect a running TIA Portal project through the Siemens Openness API.
The current implementation covers project discovery and lifecycle operations, PLC block export/import, tag table reads and guarded tag mutations, hardware/network discovery, cross-reference diagnostics, hardware catalog search, guarded network-device provisioning, and compile/check diagnostics.
Tools
The server currently exposes 10 tools.
Batch operations
execute_read_batch- run up to 50 read operations in one call. Each item carries anoperationId, anoperationname (e.g.get_block_content,list_tag_tables), and that operation's parameters. Reads run independently, so a failing item does not stop the others. Bound large reads withdepth/startPath(browse_project_tree) andmaxResults(search_equipment_catalogdefaults to 50, andread_cross_references); oversized batch responses are truncated or omitted server-side with explicit markers.preview_write_batch/apply_write_batch- preview up to 50 write operations and receive one batch-levelsafetyTokenbound to the exact ordered operation list and the combined current state, then apply them. Apply runs sequentially, stops on the first failure, and marks later itemsskipped(no transaction or rollback). Requiresconfirm=trueand thesafetyToken. Batches cover data writes (block, tag table, tag, user constant, network device); project-lifecycle operations stay single-tool only.
The batch tools are the only path for data operations. Each operation name (e.g. get_block_content, list_tag_tables, create_tag, update_block_logic, add_network_device) carries that operation's parameters as one item; a single operation is just a one-item batch.
Every operation result may carry a warnings array — non-fatal degradation notes captured from the TIA Openness worker (e.g. members skipped while reading a protected device). A populated warnings array means the payload may be partial. read_hardware_config additionally reports unreadable members in a payload-level messages array; device/module name and type-identifier fields omit values that could not be read instead of returning 0/empty-string placeholders (a few secondary name fields still fall back to an empty string, with the failure noted in messages).
Available read operations (for execute_read_batch): browse_project_tree, get_block_content, list_tag_tables, read_hardware_config, read_cross_references, search_equipment_catalog, compile_check, get_project_status.
Available write operations (for preview_write_batch / apply_write_batch): update_block_logic, create_block / delete_block, create_block_group / delete_block_group, create_tag_table / delete_tag_table, create_tag / update_tag / delete_tag, create_user_constant / update_user_constant / delete_user_constant, add_network_device, configure_network_device, start_plc / stop_plc.
Project tools
get_project_status- read active project metadata.open_project/create_project/save_project/save_project_as/archive_project/close_project- project lifecycle writes. These stay single-tool only (not batchable) and are self-previewing: call the tool WITHOUTsafetyTokento get a preview plus a single-use token, then call it again withconfirm=trueand the token to apply.
Write safety
Every MCP write operation uses a preview-then-apply workflow. Batch data writes preview with preview_write_batch and apply with apply_write_batch. Project lifecycle writes are self-previewing: call the write tool WITHOUT safetyToken to get the preview (summary, currentStateHash, requestedInputHash, a fresh single-use safetyToken, and instructions), review it, then call the same tool again with the same arguments plus confirm=true and the safetyToken.
Safety tokens are single-use, expire 10 minutes after preview, and are bound to the exact tool name, normalized project path, target, requested input, and current project state. The server rejects missing, expired, reused, mismatched, or stale-state tokens. Successful write attempts append audit JSONL records under %LOCALAPPDATA%\TiaMcpServer\audit.
preview_write_batch issues one token for the whole batch, bound to the exact ordered operation list and the combined current state. Reordering items, changing any item's input, retargeting the project path, or a change in project state all invalidate the token. apply_write_batch re-reads the combined current state once before consuming the token, then applies items sequentially and stops on the first failure.
Architecture
TIA Portal V21 ships its Openness API as .NET Framework 4.8 assemblies. Those assemblies use .NET Framework remoting APIs that cannot run correctly inside a .NET 8 process.
This project therefore uses two processes:
TiaMcpServer- the .NET 8 MCP stdio server and .NET global tool host.TiaMcpServer.OpennessWorker- a .NET Framework 4.8 worker process that loadsSiemens.Engineering.*and talks to TIA Portal.
The MCP host keeps one persistent .NET Framework 4.8 worker process attached to TIA Portal and exchanges newline-delimited JSON over stdin/stdout. Requests are serialized, and the worker restarts automatically after a crash or timeout. Siemens DLLs are never copied into this repository or the NuGet package; the worker resolves them from the local TIA Portal V21 installation.
Requirements
- Windows
- Siemens TIA Portal V21 installed
- TIA Portal Openness installed and enabled
- Current Windows user is a member of the
Siemens TIA Opennessuser group - .NET SDK 8.0 or newer for
dotnet tool install - .NET Framework 4.8 Runtime for the Openness worker
Source builds additionally need:
- .NET SDK 8.0.4xx or newer 8.0 feature band. The repo includes
global.jsonto prefer .NET SDK 8 for builds. - .NET Framework 4.8 Developer Pack or targeting pack
By default, source builds expect Openness DLLs here:
C:\Program Files\Siemens\Automation\Portal V21\PublicAPI\V21\net48
Local developer builds prefer real TIA Portal V21 assemblies from TiaPortalV21Dir. You can override that path with the TiaPortalV21Dir MSBuild property or environment variable. It must point to the folder containing Siemens.Engineering.Base.dll and Siemens.Engineering.Step7.dll.
The repo also contains compile-time reference stubs in ref/ so CI can build and package the MCP server without installing TIA Portal. Those stubs are fallback-only when a local TIA install is not found. To force stub references for CI/package builds:
dotnet build TiaMcpServer.sln -m:1 /p:UseTiaPortalReferenceStubs=true
To force local TIA references:
dotnet build TiaMcpServer.sln -m:1 /p:UseTiaPortalReferenceStubs=false /p:TiaPortalV21Dir="C:\Program Files\Siemens\Automation\Portal V21\PublicAPI\V21\net48"
During build, the worker prints the selected reference directory:
TIA Openness compile references: C:\Program Files\Siemens\Automation\Portal V21\PublicAPI\V21\net48 (UseTiaPortalReferenceStubs=false)
Install
dotnet tool install -g TiaMcpServer
Run the installed server:
tia-mcp
To bind an MCP server process to a specific project, pass --project or set TIA_MCP_PROJECT_PATH:
tia-mcp --project C:\Projects\Line.ap21
$env:TIA_MCP_PROJECT_PATH = 'C:\Projects\Line.ap21'
tia-mcp
Once a server process is bound to a project path, later tool calls with a different projectPath are rejected. Call open_project with forceRebind=true to rebind the session, or start a new MCP session for a different customer project.
Doctor command
Run tia-mcp doctor to validate the runtime environment before using the MCP server. It checks the operating system, .NET runtimes, TIA Portal installation, Openness assemblies, user group membership, worker executable, host/worker version compatibility, running TIA Portal processes, and project binding.
tia-mcp doctor
tia-mcp doctor --json
tia-mcp doctor --verbose
tia-mcp doctor --project C:\Projects\Line.ap21
Options:
--json- emit a single JSON document to stdout.--verbose- include diagnostic evidence for each check.--project- informational project binding (does not start the MCP host).
Exit codes: 0 (no blocking failures), 1 (one or more checks failed), 2 (invalid arguments).
The package includes the openness-worker folder and required non-Siemens dependencies. It intentionally excludes Siemens.Engineering*.dll; those are loaded from the local TIA Portal installation at runtime.
Build From Source
dotnet restore TiaMcpServer.sln
dotnet build TiaMcpServer.sln -m:1
The -m:1 option serializes solution builds. The MCP host project also builds and copies the net48 Openness worker, so serialized builds avoid duplicate parallel worker builds during local development.
The source build creates the .NET 8 host and copies the .NET Framework worker into:
TiaMcpServer\bin\Debug\net8.0\openness-worker
Run Locally
Start TIA Portal V21 first and open a project, then run:
dotnet run --project TiaMcpServer
The server uses MCP over stdio, so it is normally launched by an MCP client rather than used interactively in a terminal.
You can test the Openness worker directly:
'{ "method": "browse_project_tree", "projectPath": null }' | .\TiaMcpServer.OpennessWorker\bin\Debug\net48\TiaMcpServer.OpennessWorker.exe
'{ "method": "read_hardware_config", "projectPath": null }' | .\TiaMcpServer.OpennessWorker\bin\Debug\net48\TiaMcpServer.OpennessWorker.exe
'{ "method": "read_cross_references", "projectPath": null, "crossReferenceFilter": "ObjectsWithReferences" }' | .\TiaMcpServer.OpennessWorker\bin\Debug\net48\TiaMcpServer.OpennessWorker.exe
'{ "method": "search_equipment_catalog", "query": "1516", "projectPath": null }' | .\TiaMcpServer.OpennessWorker\bin\Debug\net48\TiaMcpServer.OpennessWorker.exe
Expected successful response shape:
{"success":true,"payload":"[...]"}
Expected error response shape:
{"success":false,"error":"No running TIA Portal V21 instance found. Please start TIA Portal before using the MCP server."}
Local MCP Sandbox Testing
For the safest local MCP test loop, use the official MCP Inspector against a disposable copy of a TIA project. The Inspector runs your server as a child stdio process and lets you list/call tools without adding the server to a daily-use AI client.
-
Start TIA Portal V21.
-
Open a test project, preferably a copied
.ap21project, not a production project. -
Build the repo:
dotnet restore TiaMcpServer.sln dotnet build TiaMcpServer.sln -m:1 -
Launch MCP Inspector against the built server:
npx -y @modelcontextprotocol/inspector dotnet .\TiaMcpServer\bin\Debug\net8.0\TiaMcpServer.dll
To bind the inspector session to a specific project path instead of the currently open TIA project:
npx -y @modelcontextprotocol/inspector dotnet .\TiaMcpServer\bin\Debug\net8.0\TiaMcpServer.dll --project C:\Projects\Sandbox\Line.ap21
In the Inspector UI:
- Open the Tools tab.
- Click
List Toolsand verify the 10 tools appear. - Start with reads: call
execute_read_batchwith anoperationsarray (each item has anoperationId, anoperationname such asbrowse_project_tree/list_tag_tables/read_hardware_config/read_cross_references/compile_check, and that operation's parameters). - Use a
search_equipment_catalogread item before hardware insertion so you can copy an exacttypeIdentifier. - Use a
get_block_contentread item on a block path returned bybrowse_project_tree. - Use
get_project_statusbefore lifecycle changes. - Avoid writes unless the project is disposable or backed up. Writes go through
preview_write_batch, thenapply_write_batchwithconfirm=trueand the returned batchsafetyToken.
Recommended read smoke-test for execute_read_batch (independent items; a failing item does not stop the others):
{
"operations": [
{ "operationId": "tree", "operation": "browse_project_tree" },
{ "operationId": "hw", "operation": "read_hardware_config" },
{ "operationId": "compile", "operation": "compile_check" },
{ "operationId": "xref", "operation": "read_cross_references", "filter": "ObjectsWithReferences", "plcName": "PLC_1" },
{ "operationId": "catalog", "operation": "search_equipment_catalog", "query": "1516" }
]
}
Large projects can return large JSON from cross-reference diagnostics; narrow each read item with plcName and filter. To test explicit project binding, set projectPath on each operation item (all write items in a batch must target the same project path):
{
"operations": [
{ "operationId": "tree", "operation": "browse_project_tree", "projectPath": "C:\\Projects\\Sandbox\\Line.ap21" }
]
}
For writes, preview first with preview_write_batch to obtain one batch-level safetyToken, for example a device add plus its network configuration in order:
{
"operations": [
{
"operationId": "add",
"operation": "add_network_device",
"typeIdentifier": "OrderNumber:6ES7 510-1DJ01-0AB0/V2.0",
"deviceName": "PLC_1",
"deviceItemName": "PLC_1"
},
{
"operationId": "configure",
"operation": "configure_network_device",
"deviceName": "PLC_1",
"ipAddress": "192.168.0.10",
"subnetMask": "255.255.255.0",
"pnDeviceName": "plc-1",
"subnetName": "PN/IE_1"
}
]
}
Then apply with apply_write_batch, passing the same operations array unchanged plus the returned token (apply runs sequentially and stops on the first failure):
{
"operations": [
{
"operationId": "add",
"operation": "add_network_device",
"typeIdentifier": "OrderNumber:6ES7 510-1DJ01-0AB0/V2.0",
"deviceName": "PLC_1",
"deviceItemName": "PLC_1"
},
{
"operationId": "configure",
"operation": "configure_network_device",
"deviceName": "PLC_1",
"ipAddress": "192.168.0.10",
"subnetMask": "255.255.255.0",
"pnDeviceName": "plc-1",
"subnetName": "PN/IE_1"
}
],
"confirm": true,
"safetyToken": "<token from preview_write_batch>"
}
A tag write is the same flow with a one-item batch, e.g. preview_write_batch then apply_write_batch over:
{
"operations": [
{
"operationId": "tag",
"operation": "create_tag",
"plcName": "PLC_1",
"tableName": "StandardTags",
"name": "StartButton",
"dataType": "Bool",
"logicalAddress": "%I0.0"
}
]
}
Project lifecycle writes remain single-tool and self-previewing. First call open_project with only the project path to receive the preview and token:
{
"projectPath": "C:\\Projects\\Sandbox\\Line.ap21"
}
Then call open_project again with the same arguments plus confirm=true and the returned token:
{
"projectPath": "C:\\Projects\\Sandbox\\Line.ap21",
"confirm": true,
"safetyToken": "<token from the preview call>"
}
Use archive mode values None, DiscardRestorableData, Compressed, or DiscardRestorableDataAndCompressed.
Local Package Build
The package is already published on NuGet. Use this section only when testing package changes locally before publishing a new version.
Create a local tool package:
dotnet pack TiaMcpServer\TiaMcpServer.csproj -c Release
Install from the generated package source:
dotnet tool install -g TiaMcpServer --add-source .\TiaMcpServer\bin\Release
Run the installed server:
tia-mcp
To bind an MCP server process to a specific project, pass --project or set TIA_MCP_PROJECT_PATH:
tia-mcp --project C:\Projects\Line.ap21
$env:TIA_MCP_PROJECT_PATH = 'C:\Projects\Line.ap21'
tia-mcp
Once a server process is bound to a project path, later tool calls with a different projectPath are rejected. Call open_project with forceRebind=true to rebind the session, or start a new MCP session for a different customer project.
Block Paths
Prefer block paths returned in browse_project_tree node Details.Path values. Supported block path forms are:
BlockName
PLC_1/BlockName
PLC_1/Blocks/Folder/SubFolder/BlockName
PLC_1/Units/UnitName/Blocks/Folder/SubFolder/BlockName
Legacy BlockName and PLC_1/BlockName paths are accepted only when the block name is unambiguous. If more than one block has the same name, use the deterministic Path returned by browse_project_tree.
MCP Client Configuration
Configure your MCP client to launch the tool command:
{
"mcpServers": {
"tia-portal": {
"command": "tia-mcp"
}
}
}
For local development without installing the tool, point the client at dotnet:
{
"mcpServers": {
"tia-portal-dev": {
"command": "dotnet",
"args": ["run", "--project", "{REPO PATH}\\TiaMcpServer"]
}
}
}
With an explicit project binding:
{
"mcpServers": {
"tia-portal-dev": {
"command": "dotnet",
"args": ["run", "--project", "{REPO PATH}\\TiaMcpServer", "--", "--project", "C:\\Projects\\Sandbox\\Line.ap21"]
}
}
}
Troubleshooting
System.Runtime.Remoting.RemotingException/TypeLoadExceptionin .NET 8: Siemens Openness must run in the net48 worker. Rebuild the solution and make sure the host output containsopenness-worker\TiaMcpServer.OpennessWorker.exe.- Openness DLL not found: verify TIA Portal V21 is installed and set
TiaPortalV21Dirto thePublicAPI\V21\net48folder if your install path is non-standard. - Build uses
ref/on a developer machine: verifyTiaPortalV21Dirpoints to the local V21PublicAPI\V21\net48folder, or force local references with/p:UseTiaPortalReferenceStubs=false. - No running TIA Portal instance: start TIA Portal V21 before calling tools that attach to the current project.
- Access denied or attach failure: confirm the Windows user belongs to the
Siemens TIA Opennessuser group, then sign out and back in. dotnetselects the wrong SDK: install .NET SDK 8.0.4xx or updateglobal.jsonto a locally installed .NET 8 SDK feature band.
Contributing
Contributions are welcome. See CONTRIBUTING.md for the development workflow and how to set up your environment. For architecture and build reference, see AGENTS.md.
Security
For how to report security vulnerabilities, see SECURITY.md.
No comments yet
Be the first to share your take.