/* ======================================================================== * Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.ComponentModel; using System.Threading; using System.Threading.Tasks; using ModelContextProtocol.Server; using Opc.Ua.Robotics.Client.Intent; using Opc.Ua.RobotIntent; namespace Opc.Ua.Mcp.Tools { /// /// MCP tools for direct Robot Intent control. /// [McpServerToolType] public sealed class RoboticsControlTools { /// /// Executes a Robot Intent direct-control MCP tool. /// [McpServerTool(Name = "robotics_request_control")] [Description("Requests command authority explicitly. If the server refuses because another owner holds " + "authority, the current owner is returned; this tool never synthesizes authority as a side effect.")] public static async Task RequestControlAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Session name to use; defaults to the only active session.")] string? sessionName = null, CancellationToken ct = default) { RobotIntentControllerClient resolved = await manager.ResolveControllerAsync( controller, sessionName, ct).ConfigureAwait(false); return await resolved.Transport.RequestControlAsync(ct) .ConfigureAwait(false); } /// /// Executes a Robot Intent direct-control MCP tool. /// [McpServerTool(Name = "robotics_release_control")] [Description("Releases command authority held by this session. It does not cancel work and does not infer " + "ownership; server-side errors are returned as OPC UA call errors.")] public static async Task ReleaseControlAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Session name to use; defaults to the only active session.")] string? sessionName = null, CancellationToken ct = default) { RobotIntentControllerClient resolved = await manager.ResolveControllerAsync( controller, sessionName, ct).ConfigureAwait(false); await resolved.ReleaseControlAsync(ct).ConfigureAwait(false); } /// /// Executes a Robot Intent direct-control MCP tool. /// [McpServerTool(Name = "robotics_cancel_intent")] [Description("Requests cancellation of one intent. A server refusal such as NotPermittedInMode or " + "ControlNotOwned is returned by the client API; this tool never retries or turns it into a resubmit.")] public static async Task CancelIntentAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("IntentId to cancel.")] string intentId, [Description("Stop mode requested from the server.")] StopModeEnum stopMode = StopModeEnum.QuickStop, [Description("Session name to use; defaults to the only active session.")] string? sessionName = null, CancellationToken ct = default) { RobotIntentControllerClient resolved = await manager.ResolveControllerAsync( controller, sessionName, ct).ConfigureAwait(false); return await resolved.CancelIntentAsync(intentId, stopMode, ct) .ConfigureAwait(false); } /// /// Executes a Robot Intent direct-control MCP tool. /// [McpServerTool(Name = "robotics_cancel_all")] [Description("Requests cancellation of all outstanding work. The returned count is the server's answer; " + "the MCP layer does not maintain its own outstanding-work list or retry refusals.")] public static async Task CancelAllAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Stop mode requested from the server.")] StopModeEnum stopMode = StopModeEnum.QuickStop, [Description("Session name to use; defaults to the only active session.")] string? sessionName = null, CancellationToken ct = default) { RobotIntentControllerClient resolved = await manager.ResolveControllerAsync( controller, sessionName, ct).ConfigureAwait(false); return await resolved.CancelAllAsync(stopMode, ct) .ConfigureAwait(false); } /// /// Executes a Robot Intent direct-control MCP tool. /// [McpServerTool(Name = "robotics_pause")] [Description("Requests controller pause: active Robot Intent work stops while queued " + "operations remain queued on the server. Use this to hold execution without releasing or resubmitting " + "the queue; use robotics_resume to continue a paused controller. Mode, safety, or ownership refusals " + "are returned with their IntentFailureEnum and are never retried. Command authority is never acquired " + "as a side effect. Returns IntentCommandOutcome.")] public static async Task PauseAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Session name to use; defaults to the only active session.")] string? sessionName = null, CancellationToken ct = default) { RobotIntentControllerClient resolved = await manager.ResolveControllerAsync( controller, sessionName, ct).ConfigureAwait(false); return await resolved.PauseAsync(ct).ConfigureAwait(false); } /// /// Executes a Robot Intent direct-control MCP tool. /// [McpServerTool(Name = "robotics_resume")] [Description("Requests that a paused controller continue its active operation and queued " + "work. Use this only after a pause or paused state; it does not submit new work, rebuild the queue, or " + "release authority. Mode, safety, or ownership refusals are returned with their IntentFailureEnum and " + "are never retried. Command authority is never acquired as a side effect. Returns IntentCommandOutcome.")] public static async Task ResumeAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Session name to use; defaults to the only active session.")] string? sessionName = null, CancellationToken ct = default) { RobotIntentControllerClient resolved = await manager.ResolveControllerAsync( controller, sessionName, ct).ConfigureAwait(false); return await resolved.ResumeAsync(ct).ConfigureAwait(false); } /// /// Executes a Robot Intent direct-control MCP tool. /// [McpServerTool(Name = "robotics_retry")] [Description("Requests Retry for an existing intent. The admission result preserves the exact server " + "IntentFailureEnum and message when refused; this tool performs no client-side retry loop.")] public static async Task RetryAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("IntentId to retry.")] string intentId, [Description("Session name to use; defaults to the only active session.")] string? sessionName = null, CancellationToken ct = default) { RobotIntentControllerClient resolved = await manager.ResolveControllerAsync( controller, sessionName, ct).ConfigureAwait(false); return await resolved.RetryAsync(intentId, ct) .ConfigureAwait(false); } /// /// Submits a JointMove motion intent. /// [McpServerTool(Name = "robotics_submit_joint_move")] [Description("Submits a JointMove motion intent: point-to-point joint motion to jointTargets in radians " + "or to a targetPose solved by the controller. Use this for robot-axis positioning, not Cartesian path " + "following. Refusals such as NotPermittedInMode, SafetyLimitExceeded, ControlNotOwned, " + "CapabilityNotSupported, ParameterInvalid, or QueueFull are reported with their IntentFailureEnum and " + "are never retried. Command authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitJointMoveAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("JointMove input: set jointTargets (radians) or targetPose with position/orientation.")] JointMoveIntentInput input, [Description("Axis count for local validation. Default 0 disables and leaves validation to the server.")] uint axisCount = 0, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertJointMove(input, axisCount, scope), ct); } /// /// Submits a LinearMove motion intent. /// [McpServerTool(Name = "robotics_submit_linear_move")] [Description("Submits a LinearMove motion intent: a straight Cartesian segment to target. " + "Use this for a single linear tool-centre-point move; use robotics_submit_circular_move for an arc via " + "a viaPoint and robotics_submit_cartesian_path for multiple taught waypoints. Server refusals are " + "returned with IntentFailureEnum and are never retried. Command authority is never acquired as a side " + "effect. Returns IntentSubmissionResult.")] public static Task SubmitLinearMoveAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("LinearMove input with target pose: position in metres, quaternion orientation.")] LinearMoveIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertLinearMove(input, scope), ct); } /// /// Submits a CircularMove motion intent. /// [McpServerTool(Name = "robotics_submit_circular_move")] [Description("Submits a CircularMove motion intent: a Cartesian arc through viaPoint to " + "target. Use this when the intermediate point defines circular geometry; use robotics_submit_linear_move " + "for a straight segment and robotics_submit_cartesian_path for a multi-waypoint path. Server refusals " + "are returned with IntentFailureEnum and never retried. Command authority is never acquired as a side " + "effect. Returns IntentSubmissionResult.")] public static Task SubmitCircularMoveAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("CircularMove input with viaPoint and target poses.")] CircularMoveIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertCircularMove(input, scope), ct); } /// /// Submits a Trajectory motion intent. /// [McpServerTool(Name = "robotics_submit_trajectory")] [Description("Submits a Trajectory motion intent: a complete time-parameterised joint path made of points " + "with timeFromStart, positions, and optional velocities or accelerations. Use this when the client has " + "planned the whole timed path; use robotics_submit_cartesian_path for controller-planned Cartesian " + "waypoints. CapabilityNotSupported and safety, mode, authority, or parameter refusals are returned with " + "their IntentFailureEnum and are never retried. Command authority is never acquired as a side effect. " + "Returns IntentSubmissionResult.")] public static Task SubmitTrajectoryAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Trajectory input with points array (timeFromStart, positions, optional " + "velocities/accelerations).")] TrajectoryIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertTrajectory(input, scope), ct); } /// /// Submits a CartesianPath motion intent. /// [McpServerTool(Name = "robotics_submit_cartesian_path")] [Description("Submits a CartesianPath motion intent: taught Cartesian waypoints with optional " + "per-waypoint blending, planned by the controller. Use this for multi-waypoint Cartesian paths; use " + "robotics_submit_trajectory for a timed joint trajectory supplied as points. Server refusals remain " + "results with the specific IntentFailureEnum and are never retried. Command authority is never acquired " + "as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitCartesianPathAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("CartesianPath input with waypoints array, each with pose and optional blend.")] CartesianPathIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertCartesianPath(input, scope), ct); } /// /// Submits a Force motion intent. /// [McpServerTool(Name = "robotics_submit_force")] [Description("Submits a Force motion intent: move along direction until contactForce is reached, optionally " + "limited by maxDistance and holdForce. Use this for compliant contact search or force regulation, not " + "for surface-finishing process parameters. SafetyLimitExceeded and other refusals are returned with " + "IntentFailureEnum and are never retried. Command authority is never acquired as a side effect. Returns " + "IntentSubmissionResult.")] public static Task SubmitForceAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Force input with direction (3-element unit vector), contactForce, optional frameId, " + "maxDistance, holdForce.")] ForceIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertForce(input, scope), ct); } /// /// Submits an ArcWeld process intent. /// [McpServerTool(Name = "robotics_submit_arc_weld")] [Description("Submits an ArcWeld process intent for continuous welding along the motion path, using optional " + "processProgram plus voltage, wireFeedSpeed, travelSpeed, seamTrackingEnabled, and weldProcedureRef. " + "Use this for arc-welding process execution; use robotics_submit_spot_weld for discrete weld spots. " + "Server refusals are returned with IntentFailureEnum and are never retried. Command authority is never " + "acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitArcWeldAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("ArcWeld input with optional processProgram, voltage, wireFeedSpeed, travelSpeed, " + "seamTrackingEnabled, weldProcedureRef.")] ArcWeldIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertArcWeld(input, scope), ct); } /// /// Submits a SpotWeld process intent. /// [McpServerTool(Name = "robotics_submit_spot_weld")] [Description("Submits a SpotWeld process intent for discrete resistance weld points, with processProgram " + "plus weldSchedule and gunForce. Use this for spot-welding cycles; use robotics_submit_arc_weld for a " + "continuous weld seam. Server refusals are returned with IntentFailureEnum and are never retried. " + "Command authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitSpotWeldAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("SpotWeld input with optional processProgram, weldSchedule, gunForce.")] SpotWeldIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertSpotWeld(input, scope), ct); } /// /// Submits a Dispense process intent. /// [McpServerTool(Name = "robotics_submit_dispense")] [Description("Submits a Dispense process intent for applying material along a " + "path, using optional processProgram plus flowRate, beadWidth, and purgeCycles. Use this for material " + "deposition; use robotics_submit_fasten for tightening, robotics_submit_palletise for pattern placement, " + "and robotics_submit_surface_finish for sanding or polishing contact processing. Server refusals are " + "returned with IntentFailureEnum and are never retried. Command authority is never acquired as a side " + "effect. Returns IntentSubmissionResult.")] public static Task SubmitDispenseAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Dispense input with optional processProgram, flowRate, beadWidth, purgeCycles.")] DispenseIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertDispense(input, scope), ct); } /// /// Submits a Fasten process intent. /// [McpServerTool(Name = "robotics_submit_fasten")] [Description("Submits a Fasten process intent for tightening or joining at a fastener/joint, using optional " + "processProgram plus joint NodeId, programNumber, and targetTorque. Use this for fastening operations; " + "use robotics_submit_dispense for material flow and robotics_submit_surface_finish for abrasive or " + "finishing contact. Server refusals are returned with IntentFailureEnum and are never retried. Command " + "authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitFastenAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Fasten input with optional joint NodeId, programNumber, targetTorque, processProgram.")] FastenIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertFasten(input, scope), ct); } /// /// Submits a Palletise process intent. /// [McpServerTool(Name = "robotics_submit_palletise")] [Description("Submits a Palletise process intent that places workpieces using a controller-defined pattern " + "LocationType, with layer, row, and column indexes selecting the cell. Use this for pallet pattern " + "placement; use robotics_submit_place for a single explicit destination and robotics_submit_dispense for " + "material deposition. Server refusals are returned with IntentFailureEnum and are never retried. Command " + "authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitPalletiseAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Palletise input with optional pattern NodeId, layer, row, column.")] PalletiseIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertPalletise(input, scope), ct); } /// /// Submits a SurfaceFinish process intent. /// [McpServerTool(Name = "robotics_submit_surface_finish")] [Description("Submits a SurfaceFinish process intent for sanding, polishing, deburring, or finishing, " + "using optional processProgram plus contactForce, feedRate, toolSpeed, and stepOver. Use this when the " + "process removes or finishes material; use robotics_submit_dispense to add material and " + "robotics_submit_force for force-controlled motion without process parameters. Server refusals are " + "returned with IntentFailureEnum and are never retried. Command authority is never acquired as a side " + "effect. Returns IntentSubmissionResult.")] public static Task SubmitSurfaceFinishAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("SurfaceFinish input with optional processProgram, contactForce, feedRate, " + "toolSpeed, stepOver.")] SurfaceFinishIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertSurfaceFinish(input, scope), ct); } /// /// Submits a Grasp intent. /// [McpServerTool(Name = "robotics_submit_grasp")] [Description("Submits a Grasp intent to close or activate a tool on an object, with tool NodeId and " + "force. Use this to acquire a workpiece using an end effector; use robotics_submit_release to open or " + "deactivate the tool, and robotics_submit_pick when source motion plus grasp should be one pick intent. " + "Control, safety, mode, capability, or parameter refusals are returned with IntentFailureEnum and are " + "never retried. Command authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitGraspAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Grasp input with tool NodeId and force in newtons.")] GraspIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertGrasp(input, scope), ct); } /// /// Submits a Release intent. /// [McpServerTool(Name = "robotics_submit_release")] [Description("Submits a Release intent to open or deactivate a gripper/tool by tool NodeId. Use this to " + "let go of a held object; use robotics_submit_grasp to acquire it, robotics_submit_place when " + "motion plus release should be one intent, and robotics_submit_tool_change to fit or remove a tool at a " + "dock. Server refusals are returned with IntentFailureEnum and are never retried. Command authority is " + "never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitReleaseAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Release input with tool NodeId.")] ReleaseIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertRelease(input, scope), ct); } /// /// Submits a Pick intent. /// [McpServerTool(Name = "robotics_submit_pick")] [Description("Submits a Pick intent for inbound material handling: approach the source LocationType " + "and take possession with the selected tool. Choose it when the robot must acquire a workpiece from a " + "source. Do not call it to deposit, unload, or release at a destination; that is robotics_submit_place. " + "Refusals such as ParameterInvalid, CapabilityNotSupported, SafetyLimitExceeded, ControlNotOwned, or " + "QueueFull are reported with IntentFailureEnum and never retried. Command authority is never acquired as " + "a side effect. Returns IntentSubmissionResult.")] public static Task SubmitPickAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Pick input with source NodeId, tool NodeId, and optional objectClass label.")] PickIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertPick(input, scope), ct); } /// /// Submits a Place intent. /// [McpServerTool(Name = "robotics_submit_place")] [Description("Submits a Place intent for outbound material handling: carry the held workpiece to the " + "destination LocationType and let it go with the selected tool. Choose it when the robot must deposit or " + "unload something held. Do not call it to collect from a source or close the gripper; that is " + "robotics_submit_pick or robotics_submit_grasp. Refusals are surfaced with their IntentFailureEnum, " + "without retrying or silently taking command authority. Returns IntentSubmissionResult.")] public static Task SubmitPlaceAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Place input with destination NodeId and tool NodeId.")] PlaceIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertPlace(input, scope), ct); } /// /// Submits a ToolChange intent. /// [McpServerTool(Name = "robotics_submit_tool_change")] [Description("Submits a ToolChange intent to fit a docked tool or release the current tool when tool is " + "null. Use this for changing the robot end effector; use robotics_submit_release only to open/deactivate " + "a gripper, and robotics_submit_call_program only to run a server program. Server refusals are returned " + "with IntentFailureEnum and are never retried. Command authority is never acquired as a side effect. " + "Returns IntentSubmissionResult.")] public static Task SubmitToolChangeAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("ToolChange input with optional tool NodeId and dockStation NodeId.")] ToolChangeIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertToolChange(input, scope), ct); } /// /// Submits a SetOutput intent. /// [McpServerTool(Name = "robotics_submit_set_output")] [Description("Submits a SetOutput intent that writes a controller OutputSignalType to a supplied value. " + "Use it for typed outputs, not robot motion or program execution. The server " + "validates the output NodeId and value DataType; refusals are returned with IntentFailureEnum and are " + "never retried. Command authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitSetOutputAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("SetOutput input with output NodeId and typed value.")] SetOutputIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertSetOutput(input, scope), ct); } /// /// Submits a CallProgram intent. /// [McpServerTool(Name = "robotics_submit_call_program")] [Description("Submits a CallProgram intent that starts a server ProgramType by program NodeId with optional " + "arguments. Use it only for a controller program; use robotics_submit_place for " + "placing objects and robotics_submit_tool_change for changing end effectors. A program NodeId naming " + "anything else is refused. Server refusals are returned with IntentFailureEnum and are never retried. " + "Command authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitCallProgramAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("CallProgram input with program NodeId and optional arguments.")] CallProgramIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertCallProgram(input, scope), ct); } /// /// Submits a Wait intent. /// [McpServerTool(Name = "robotics_submit_wait")] [Description("Submits a Wait intent that delays for a duration in seconds or until a signal/Boolean node " + "under the controller satisfies the server's wait condition. Use this to insert timing or signal waits " + "in a queue or mission; it does not poll the MCP client, watch an existing operation, or command motion. " + "Signal, duration, mode, safety, or authority refusals are returned with IntentFailureEnum and are never " + "retried. Command authority is never acquired as a side effect. Returns IntentSubmissionResult.")] public static Task SubmitWaitAsync( RoboticsIntentManager manager, [Description(ControllerDescription)] string controller, [Description("Wait input with duration in seconds and optional signal NodeId.")] WaitIntentInput input, [Description(SessionNameDescription)] string? sessionName = null, CancellationToken ct = default) { return SubmitAsync( manager, controller, sessionName, scope => RoboticsIntentDtoConverter.ConvertWait(input, scope), ct); } internal const string ControllerDescription = "Controller selector: unique display name or BrowseName (e.g. 'Controller1') or OPC UA " + "NodeId string (e.g. ns=2;s=RobotIntent/Controllers/Controller1). Matched with exact " + "ordinal comparison after trimming. Fails with available names and NodeIds when zero " + "or multiple controllers match. Names of frames, tools, locations, outputs, programs, " + "and axes inside the request are resolved against this controller's published lookup " + "tables; full NodeIds are always accepted and validated by the server."; private const string SessionNameDescription = "Optional MCP OPC UA session name. Omit it only when exactly one OPC UA session is active; provide the " + "name returned by the session-management tools when multiple sessions are connected. If the name is " + "missing, ambiguous, or unknown, the tool fails before sending any robot command."; private static async Task SubmitAsync( RoboticsIntentManager manager, string controller, string? sessionName, Func convert, CancellationToken ct) { RoboticsResolutionContext context = await RoboticsResolutionContext.CreateAsync( manager, controller, sessionName, ct).ConfigureAwait(false); IntentDataType intent = convert(context.Scope); return await context.Client.TrySubmitIntentAsync(intent, ct).ConfigureAwait(false); } } }