Skip to main content
Glama

tcgen-st-test-mcp

Offline semantic validation for TcGen Structured Text review bundles.

This MCP server accepts inline TcGen review-ST sources, normalizes the supported subset into STruC++-compatible ST, builds either JSON-spec tests or TcGen framework-style tests, and runs them through the strucpp CLI in a temporary workspace.

Passing results mean only:

Offline semantic test passed for the normalized STruC++ model. Final TwinCAT compilation or target validation may still be required for vendor libraries, task behavior, I/O, ADS, motion, lifecycle methods, and runtime-specific behavior.

Every source-processing request must set candidateSourcePath to the exact sources[].path of the staged candidate. Exactly one source must match. Reports bind that candidate to the result through:

{
  "subject": {
    "candidateSourcePath": "cut.st",
    "candidateSha256": "...",
    "dependencyBundleSha256": "...",
    "discoveredFrameworkTests": ["FB_Test_LimitCounter"],
    "selectedFrameworkTests": ["FB_Test_LimitCounter"]
  }
}

Semantic generation and run results use report schema v2. The execution contract adds explicit coverage and generated-test identity alongside the candidate/dependency hashes:

{
  "schemaVersion": 2,
  "testMode": "generated",
  "coveredExecutableObjects": ["FB_Adder"],
  "generatedTestNames": ["adds two integers"],
  "hashes": {
    "testSource": "..."
  }
}

testMode is generated for JSON testSpec requests and framework for TcGen framework requests. Generated coverage names the declared target POU. Framework coverage names only the concrete FB_Test_* blocks executed by the offline wrappers; it does not infer production-object coverage. The legacy v1 run contract remains published as schemas/semantic-report-v1.schema.json.

The candidate hash covers its exact UTF-8 content. The dependency hash covers JSON.stringify of the remaining {path, content} entries sorted ordinally by path and then content, so source-array ordering cannot change the identity. The packaged schemas/dependency-bundle-hash-vectors.json contract publishes exact canonical JSON and lowercase SHA-256 vectors for cross-language implementations, including JavaScript-specific escaping and UTF-8 behavior.

For tcgen_st_test_run, scope may narrow dependencies but cannot exclude any object parsed from candidateSourcePath. The run report's normalization.includedObjects lists only objects actually emitted into the compiled source; candidate objects excluded by scope or normalization block the run.

Tools

  • tcgen_st_backend_check

  • tcgen_st_normalize

  • tcgen_st_test_generate

  • tcgen_st_test_run

Related MCP server: codeviewer-mcp

CLI

tcgen-st-test backend-check
tcgen-st-test normalize request.json
tcgen-st-test generate request.json
tcgen-st-test run request.json

Test Modes

tcgen_st_test_generate and tcgen_st_test_run accept exactly one test authority:

  • testSpec: the original JSON step format, converted into STruC++ TEST blocks.

  • frameworkTest: TcGen/TwinCAT-style ST tests where the request sources include the CUT plus concrete FB_Test_* EXTENDS FB_TestCaseBase blocks.

When options.includeArtifacts is true, run reports include the generated ST in artifacts.generatedTestFile even when preflight blocks execution. Compiler stdout, stderr, diagnostics, and assertion messages have terminal control sequences removed and temporary workspace roots replaced with <workspace>. Non-passing compiler/runtime output is also promoted into structured report diagnostics so consumers do not need to relay raw artifact streams. An exit-0 backend result with no parsed tests is rejected as backend_error.

Every testSpec.tests[].name must be unique ignoring case and surrounding whitespace. Duplicate names block generation and execution because result evidence must identify each generated test unambiguously.

Framework mode uses:

{
  "frameworkTest": {
    "mode": "tcgen-test-framework",
    "executionContract": "tcgen-framework-multiscan-v1",
    "testFunctionBlocks": ["FB_Test_LimitCounter"],
    "targetMappings": [
      {
        "testFunctionBlock": "FB_Test_LimitCounter",
        "productionTarget": "FB_LimitCounter",
        "testSourcePath": "test.st",
        "testSourceSha256": "<lowercase SHA-256 of the exact test.st content>"
      }
    ],
    "maxScans": 200
  }
}

When testFunctionBlocks is present it must name every discovered submitted framework test. To focus a run, submit only the relevant test sources rather than selecting an arbitrary passing subset. Reports expose both discoveredFrameworkTests and selectedFrameworkTests for independent checks.

targetMappings is mandatory and one-to-one. Every selected FB_Test_* and every executable object in candidateSourcePath must be represented exactly once. The MCP verifies the exact source path and UTF-8 content SHA-256, confirms that the test source instantiates or references the production target outside comments and strings, and requires at least one m_xAssert* call over an observed value rather than a literal-only smoke assertion. Framework runner registration programs and framework infrastructure are not production targets.

Generation keeps the submitted TwinCAT framework ST byte-for-byte in testFile and frameworkTestFiles. The separately generated STruC++ execution adapter is returned in generatedTestFile; hashes.testSource identifies that adapter, while each framework source retains its own trusted mapping hash. Both generate and run responses also expose artifactIdentities[]. Canonical submitted sources use role: "framework_st", visibility: "review", while the separate adapter uses role: "execution_adapter", visibility: "technical". Each identity includes a stable artifact ID, path, content SHA-256, UTF-8 byte length, and primary flag; consumers must select the reviewable source by role rather than by the legacy field name. Semantic report v2 publishes the verified identities and structural evidence in frameworkTargetCoverage[] (assertionCount, targetReferenceCount, and verified). Generate and run tool metadata advertise the frameworkTargetCoverageV1, frameworkMultiScanV1, twinCatShortCircuitOperatorsV1, twinCatBistableAliasesV1, frameworkAssertionLedgerV1, frameworkAssertionProgressV1, frameworkLifecycleV1, frameworkArtifactRolesV1, frameworkAssertionRunningProgressV1, and (on tcgen_st_test_run) candidateCompilePreflightV1 capabilities. The candidate-preflight capability means the trusted scheduler can compile the exact candidate and dependency bundle with a deterministic internal smoke specification before spending a model call on Framework ST design. The short-circuit capability requires the pinned STruC++ distribution to compile TwinCAT AND_THEN and OR_ELSE natively with lazy right-hand-side evaluation; the MCP never rewrites them to eager operators. The required tcgen-framework-multiscan-v1 contract calls m_xExecute(TRUE) once, then calls m_xExecute(FALSE) once per offline PLC scan while m_xIsBusy() remains true. The offline task interval is deterministic: each resumed scan is preceded by ADVANCE_TIME(1000000) (one millisecond), allowing IEC timers to progress without introducing STruC++ syntax into the submitted Beckhoff ST. Synchronous tests that finish on the initial call remain valid. Successful terminal paths set both _eState and _eExecuteState to Passed and clear _xPhaseBusy. A multi-scan test declares udiStep : UDINT, advances through CASE udiStep OF on FALSE-trigger calls only while both inherited states remain Running, and clears _xPhaseBusy on success and failure. Its m_xIsBusy() implementation is the side-effect-free _xPhaseBusy observer. The private adapter independently asserts the final ST_TestCaseResult states and counters.

The additive assertions[] field identifies every meaningful submitted m_xAssert* call by source path, source SHA-256, one-based line, mapped target, stable assertionId, and stable checkpoint identity. Generation reports use status: "not_run". The private offline adapter records every dynamic assertion invocation in a bounded ledger and emits one independent checkpoint per source assertion. A single STruC++ invocation therefore returns complete passed, failed, or not_reached evidence without changing the submitted Framework ST. The parent wrapper and every checkpoint execute a fresh test-FB/CUT instance; checkpoint tests never query assertion state left behind by another TEST. The adapter binds runtime rows to stable assertion IDs rather than treating the human-readable assertion message as identity. Reached rows include start and completion timestamps, and unreached rows are explicit rather than inferred as passing. Request-bound progress emits queued, running, and completed checkpoint phases with the canonical Framework source line so a client can highlight the currently executing assertion without displaying the execution adapter. assertionLedger reports the stable ledger hash, completeness, and counts while the legacy first-error result remains available. Report v1 remains unchanged.

In framework mode the MCP normalizes the CUT and agent-authored test FBs and replaces uploaded framework infrastructure such as FB_TestRunner, I_TestCase, FB_TestCaseBase, and GVL_TestResults with a compact STruC++-compatible shim. A PROGRAM containing both FB_TestRunner and m_udiRegisterTest is structurally recognized as runner registration and rewritten to a compiled offline registration surrogate; ordinary production PROGRAM MAIN objects remain unchanged. Generated wrapper TEST blocks then execute every concrete FB_Test_* instance. A failing assertion is returned as verdict: "failed" with a human-readable aggregate in tests[].message and the exact per-assertion cause in assertions[] and assertionLedger. Semantic report v2 also exposes backend.executionAttempted, distinguishing a normalization/preflight rejection from an invocation that reached STruC++. backend.standardFunctionBlockContracts is loaded from the compiler-generated libs/iec-function-block-contracts.json sidecar. Preflight validates the sidecar schema, library/contract versions, its canonical payload byte count and SHA-256, every machine-readable pin signature and alias collision, and the RS/SR TwinCAT pin order and dominance semantics. The installed runtime manifest must contain and hash this sidecar; a missing, altered, or incompatible contract fails closed. A compiler rejection of an admitted TwinCAT pin is reported as backend_incompatibility, never as a candidate bug.

The stdio server runs independent tool requests concurrently so Virtual Tests for parallel workers are not serialized by the MCP transport. Callers that send an MCP progress token receive request-bound phase notifications. MCP cancellation notifications abort the matching active semantic child process; progress tokens, grants, and authorization data are never forwarded to backend children. Assertion completion notifications are emitted as backend result lines arrive; the authoritative report remains the final complete ledger.

Local Development

Use Node.js 22 or later.

npm ci
npm run build
npm run bundle
npm run build:exe

npm run bundle produces the self-contained Node.js 22 MCP runtime at dist/tcgen-st-test-mcp.cjs. It includes runtime dependencies and can be copied into the TcGen product pack without node_modules or a sibling checkout. npm run build:exe additionally produces dist/tcgen-st-test-mcp.exe, a Windows x64 executable containing its own Node.js 22 runtime. The product launcher should use this executable; the CJS bundle remains useful for npm and source-development workflows.

For local development against a sibling STruC++ checkout:

git clone https://github.com/AgileAutomationTechnologies/STruCpp.git C:\Users\fboid\source\python\STruCpp
cd C:\Users\fboid\source\python\STruCpp
git checkout development

$env:STRUCPP_PATH = "C:\Users\fboid\source\python\STruCpp"
$env:STRUCPP_GPP_PATH = "C:\msys64\ucrt64\bin\g++.exe"

STRUCPP_PATH may point to a strucpp executable, dist/node/cli.js, or the STruC++ repository root. STRUCPP_GPP_PATH is optional for backend checks, but tcgen_st_test_run needs a working g++ for STruC++ --test execution. Inside the standalone MCP executable, a repository/JavaScript STruC++ path also requires a real external Node executable through TCGEN_ST_NODE_PATH or PATH; the packaged MCP refuses to recursively use itself as Node. Prefer native strucpp-win.exe in product installations, which needs no external Node.

The v0.8.6 Windows validation target is the AgileAutomationTechnologies STruC++ downstream release at commit 8b8d07542a3ece531fe7e66c900a08549036aa01, based on upstream STruC++ 0.5.13 plus the qualified TcGen downstream patch set, identified as 0.5.13-tcgen.6. Backend checks and semantic runs fail closed when the detected STruC++ version is missing or differs from the complete 0.5.13-tcgen.6 distribution version.

Verification

npm run verify
npm run verify:native
npm run fixtures
npm run smoke:mcp
npm run pack:check

Release automation can export a deterministic, complete Framework assertion report contract after building the package:

npm run export:semantic-report-contract -- C:\temp\mcp-0.8.6-framework-assertion-report.json

The exported JSON contains the current mcpVersion and intentionally omits mcpCommit. The release orchestrator must inject the immutable commit from the published MCP revision rather than asking the exporter to infer it from a possibly dirty working tree.

Native verification expects:

$env:STRUCPP_PATH = "C:\Users\fboid\source\python\STruCpp"
$env:STRUCPP_GPP_PATH = "C:\msys64\ucrt64\bin\g++.exe"

Reference STruC++ checks:

cd C:\Users\fboid\source\python\STruCpp
npm run build
npm run typecheck
node dist\node\cli.js --version
node dist\node\cli.js tests\st-validation\function_blocks\fb_accumulator.st --gpp C:\msys64\ucrt64\bin\g++.exe --test tests\st-validation\function_blocks\test_fb_accumulator.st
node dist\node\cli.js tests\st-validation\function_blocks\basic_fb.st --no-default-libs --gpp C:\msys64\ucrt64\bin\g++.exe --test tests\st-validation\function_blocks\test_basic_fb.st

Workspace Retention

Temporary workspaces are deleted by default. keepWorkspace only returns a workspace path when both are true:

  • the request sets options.keepWorkspace = true;

  • the local environment sets TCGEN_ST_ALLOW_KEEP_WORKSPACE=true.

Without that environment flag, keepWorkspace is ignored and a non-blocking SANDBOX_KEEP_WORKSPACE_DISABLED diagnostic is returned.

Known STruC++ Limitation

The upstream basic_fb sample declares Toggle, while bundled OSCAT libraries also define TOGGLE. Run that sample with --no-default-libs; use fb_accumulator as the default-library native smoke until library shadowing is handled upstream.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

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/AgileAutomationTechnologies/tcgen-st-test-mcp'

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