This project exposes APS 2-ID-D MIC controls through FastMCP over HTTP and executes beamline actions through Bluesky QueueServer.
MCP client -> FastMCP HTTP server -> REManagerAPI -> Bluesky QueueServer
The MCP service is a thin allowlisted control layer over QueueServer. It does not run a local RunEngine and it does not expose arbitrary QueueServer plans or functions.
Install the package into the target environment:
pip install -e .The environment needs:
fastmcpbluesky-queueserver-api
It does not require the old local worker/ZMQ stack.
A repo-level config.toml provides the default MCP server configuration, including:
- host/port/path
- allowable
x,y, andzranges - QueueServer addresses
- approved QueueServer plan names for acquisition and motion
CLI flags still override TOML values when needed.
Start the MCP server with the repo config:
control-suite-aps-2idd-mcp --config config.tomlIf QueueServer is running on the same host as the MCP server, keep the default
127.0.0.1 QueueServer addresses in config.toml. Otherwise, update the
[qserver] section.
You can still override specific values from the command line, for example:
control-suite-aps-2idd-mcp --config config.toml --qserver-control-addr tcp://[hostname]:60615 --qserver-info-addr tcp://[hostname]:60625 --allowable-x-range 0,50 --allowable-y-range 0,50Clients should connect to:
http://127.0.0.1:8050/mcp
aps2idd_control.health()aps2idd_control.get_state()aps2idd_control.get_current_mda_file()aps2idd_control.get_save_data_path()aps2idd_control.get_global_health_snapshot()aps2idd_control.recover_detector(device_name, retries=1)aps2idd_control.acquire_image(width, height, x_center, y_center, stepsize_x, stepsize_y, dwell_ms=None)aps2idd_control.process_image(current_mda_file, save_data_path=None, plot_in_log_scale=None, show_colorbar=None, channels=None)aps2idd_control.dump_array(buffer_name)aps2idd_control.acquire_line_scan(positioner_name, length, stepsize, center=0, sample_x=None, sample_y=None, sample_z=None, energy=None, dwell_ms=None)aps2idd_control.move_sample(axis, position)aps2idd_control.move_zp_z(position)aps2idd_control.set_parameters(parameters)aps2idd_control.get_attribute_payload(name)
After aps2idd_control.acquire_line_scan() succeeds, the service keeps
image_0, image_km1, and image_k buffers plus matching psize_0,
psize_km1, and psize_k values inferred from the line-scan step size.
aps2idd_control.dump_array() returns those image buffers as base64-encoded
NumPy payloads.
For an HTTP MCP client:
{
"mcpServers": {
"control-suite-aps-2idd": {
"url": "http://127.0.0.1:8050/mcp",
"transport": "http"
}
}
}- Scan dimensions, positions, and step sizes are in microns unless noted otherwise.
aps2idd_control.move_zp_z(position)drives the zone-plate z positioner, validated againstallowable_zp_range(distinct from the sample z motor'sallowable_z_range).aps2idd_control.set_parameters(parameters)is equivalent, usingparameters[0]as the zp-z target (also validated againstallowable_zp_range).- Motion and acquisition tools are QueueServer plans, submitted with
item_execute. Plans return anitem_uid(not atask_uid); the service waits for the RE manager to return to idle and reads the outcome from QueueServer history.task_uidis only produced by QueueServer functions (e.g.get_save_data_path). aps2idd_control.get_global_health_snapshot()returns a beamline + scan device snapshot ({"timestamp": ..., "devices": {name: {"pvs": {...}}}}) coveringring,sample,scanrecord, the area detectors, andfly_dwell. It is the observable for beamline/scan health monitoring and is safe to poll while a scan runs (executed as a background QueueServer function). The device set is driven by the beamline monitor manifest (qserver.beamline_monitor_manifest).aps2idd_control.recover_detector(device_name, retries=1)attempts to unhang a stalled area detector (e.g.xmap,xp3,eiger). If a plan is running it pauses the RE (immediate), resets the detector through the allowlisted QueueServerrecover_detectorfunction, then resumes; an already-paused RE is left paused. The result includesdevice,success, and aprogressstep list.aps2idd_control.acquire_imageandaps2idd_control.acquire_line_scanstream live scan progress as MCP progress notifications, sourced from the QueueServer console (ZMQ info) output. Their results reportitem_uid,run_uids,scan_ids,save_data_path, andcurrent_mda_file.current_mda_fileis captured before the plan runs (the savedatanext_file_nameauto-increments once the scan starts), so it names the MDA file that scan actually wrote.- After the scan, acquisition tools process the MDA/HDF5 output and write
file artifacts.
acquire_imagereturns absoluteimg_pathandraw_data_pathfields for the rendered PNG and 2D.npyarray.acquire_line_scanreturns absoluteimg_pathandraw_data_pathfields for the plotted line profile and.npyprofile data, plusgaussian_fit_paramswithfwhm,a,mu,sigma,c,normalized_residual,x_min, andx_max. Successful line scans also update the in-processimage_0,image_km1,image_k,psize_0,psize_km1, andpsize_kattributes. aps2idd_control.process_imageruns the same postprocessing asaps2idd_control.acquire_imageon an existing MDA file, so already-acquired data can be (re)visualized without a new scan — no beamline motion and no QueueServer plan.save_data_pathdefaults to the current QueueServer save path, andplot_in_log_scale,show_colorbar, andchannelsdefault to the service configuration. It returnsimg_path,raw_data_path,channel,h5_path,mda_path,save_data_path, andcurrent_mda_file.aps2idd_control.acquire_line_scandrives the axis named bypositioner_name(x,y,z, orenergy);length,center, andstepsizeare in that positioner's units (microns for x/y/z, keV for energy).centeris a relative offset from the positioner's position at scan time (e.g.center=0scans symmetrically around the current position). Thestep1d_scanrecordplan moves the sample/energy to the optionalsample_x/sample_y/sample_z/energypositions (current position is kept for any left unset) before scanning.- Dwell time per point: pass
dwell_msto override per call; when omitted the acquisition uses the configureddwell_imaging(images) ordwell_line_scan(line scans) value. - Range validation: any explicitly provided absolute position
(
sample_x/sample_y/sample_z/energy) is validated against its axis range (allowable_x/y/z_rangein microns,allowable_energy_rangein keV). The relative scan extent (target + center ± length/2) is only validated when the driven axis's absolute target is supplied; with no target the current position is unknown, so the absolute extent cannot be checked.