set_protocol_steps
Replace all steps in a protocols.io protocol by providing a new steps list with descriptions, materials, and references.
Instructions
Replace the entire steps list of a specific protocol by its protocol ID with a new steps list. The existing steps will be completely overwritten.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| protocol_id | Yes | Unique identifier for the protocol | |
| steps | Yes | List of steps to set for the protocol |
Implementation Reference
- The core handler function for the set_protocol_steps tool. Decorated with @mcp.tool() for registration. It deletes existing steps, creates new ones from the input list using ProtocolStepInput, links them sequentially, and returns the updated list of ProtocolStep objects.@mcp.tool() async def set_protocol_steps( protocol_id: Annotated[int, Field(description="Unique identifier for the protocol")], steps: Annotated[list[ProtocolStepInput], Field(description="List of steps to set for the protocol")] ) -> list[ProtocolStep] | ErrorMessage: """ Replace the entire steps list of a specific protocol by its protocol ID with a new steps list. The existing steps will be completely overwritten. """ if not steps: return ErrorMessage.from_string("At least one step is required to set the protocol steps.") # get all existing steps response_get_steps = await helpers.access_protocols_io_resource("GET", f"/v4/protocols/{protocol_id}/steps?content_format=markdown") if response_get_steps["status_code"] != 0: return ErrorMessage.from_string(response_get_steps['status_text']) step_existed = [ProtocolStep.from_api_response(step) for step in response_get_steps.get("payload", [])] # delete all existing steps response_delete_protocol_step = await helpers.access_protocols_io_resource("DELETE", f"/v4/protocols/{protocol_id}/steps", {"steps": [step.id for step in step_existed]}) if response_delete_protocol_step["status_code"] != 0: return ErrorMessage.from_string(response_delete_protocol_step['status_text']) # set steps data = [] previous_step_id = None for step in steps: step_content = await ProtocolStepInput.to_string(step) step_data = { "guid": uuid.uuid4().hex, "previous_guid": previous_step_id, "step": step_content } data.append(step_data) previous_step_id = step_data["guid"] response_set_steps = await helpers.access_protocols_io_resource("POST", f"/v4/protocols/{protocol_id}/steps", {"steps": data}) if response_set_steps["status_code"] != 0: return ErrorMessage.from_string(response_set_steps['status_text']) # get updated steps response_get_steps = await helpers.access_protocols_io_resource("GET", f"/v4/protocols/{protocol_id}/steps?content_format=markdown") if response_get_steps["status_code"] != 0: return ErrorMessage.from_string(response_get_steps['status_text']) protocol_steps = [ProtocolStep.from_api_response(step) for step in response_get_steps.get("payload", [])] return protocol_steps
- Pydantic model defining the input structure for protocol steps, used in the tool's 'steps' parameter. Includes methods for string conversion.class ProtocolStepInput(BaseModel): description: Annotated[str, Field(description="Description of the step (plain text only)")] materials: Annotated[list[Material], Field(description="Materials required for this step. Empty if no materials are needed")] = Field(default_factory=list) reference_protocol_ids: Annotated[list[int], Field(description="Protocol IDs referenced by this step. Empty if no references exist. Strongly recommend using at least one reference to ensure credibility")] = Field(default_factory=list) @staticmethod async def to_string(step: "ProtocolStepInput") -> str: step_content = f"{step.description}\n" if len(step.materials) + len(step.reference_protocol_ids) > 0: step_content += "\n" # add materials to step content if len(step.materials) > 0: step_content += "[Materials]\n" for material in step.materials: step_content += f"- {material.name.replace(' ', '_')} {material.quantity} {material.unit.replace(' ', '_')}\n" # add reference protocols to step content if len(step.reference_protocol_ids) > 0: if len(step.materials) > 0: step_content += "\n" step_content += "[Protocol References]\n" for protocol_id in step.reference_protocol_ids: # get information about the referenced protocol response_get_protocol = await helpers.access_protocols_io_resource("GET", f"/v4/protocols/{protocol_id}") if response_get_protocol["status_code"] != 0: return response_get_protocol["status_text"] protocol = await Protocol.from_protocol_id(response_get_protocol["payload"]["id"]) step_content += f"- {protocol.title.replace('[', '<').replace(']', '>')}[{protocol.id}] {protocol.doi}\n" return step_content
- Pydantic model for output ProtocolStep objects, parsed from API responses and used as return type (list thereof). Includes parsing logic from string format.class ProtocolStep(BaseModel): id: Annotated[str, Field(description="Unique identifier for the step")] description: Annotated[str, Field(description="Description of the step")] materials: Annotated[list[Material], Field(description="Materials required for this step. Empty if no materials are needed or if source data could not be parsed")] = Field(default_factory=list) reference_protocol_ids: Annotated[list[int], Field(description="Protocol IDs referenced by this step. Empty if no references exist or if source data could not be parsed")] = Field(default_factory=list) @staticmethod def parse(step: str) -> dict: description = [] materials = [] reference_protocol_ids = [] material_flag = False reference_flag = False for line in step.splitlines(): if material_flag is False and reference_flag is False: if line == "[Materials]": material_flag = True reference_flag = False elif line == "[Protocol References]": reference_flag = True material_flag = False elif len(line) > 0: description.append(line) elif material_flag: if len(line) == 0 or line[0] != '-': material_flag = False continue data = line.split() materials.append(Material( name=data[1], quantity=float(data[2]), unit=data[3] )) elif reference_flag: if len(line) == 0 or line[0] != '-': reference_flag = False continue data = line.split("[")[1].split("]")[0] reference_protocol_ids.append(int(data)) description = "\n".join(description) return { "description": description, "materials": materials, "reference_protocol_ids": reference_protocol_ids } @classmethod def from_api_response(cls, data: dict) -> "ProtocolStep": parsed_step = ProtocolStep.parse(data["step"]) return cls( id=data["guid"], description=parsed_step["description"], materials=parsed_step["materials"], reference_protocol_ids=parsed_step["reference_protocol_ids"] )