Skip to main content
Glama
rkirkendall

Medplum MCP Server

by rkirkendall

updateObservation

Modify existing observations in Medplum FHIR servers by providing the observation ID and updating fields like status, numeric value, or string value using the MCP server.

Instructions

Updates an existing observation. Requires the observation ID and the fields to update.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
observationIdYesThe unique ID of the observation to update.
statusNoNew status for the observation.
valueQuantityNoNew numeric value of the observation. Optional.
valueStringNoNew string value of the observation. Optional.

Implementation Reference

  • The core handler function that updates an existing Observation FHIR resource. It reads the current resource, applies partial updates with special handling for fields like note, identifier, references (subject, encounter, performer), ensures value[x] exclusivity, converts nulls to undefined, and calls Medplum's updateResource.
    export async function updateObservation(observationId: string, updates: UpdateObservationArgs): Promise<Observation> {
      await ensureAuthenticated();
    
      if (!observationId) {
        throw new Error('Observation ID is required to update an observation.');
      }
      if (!updates || Object.keys(updates).length === 0) {
        throw new Error('Updates object cannot be empty for updating an observation.');
      }
    
      const existingObservation = await medplum.readResource('Observation', observationId);
      if (!existingObservation) {
        throw new Error(`Observation with ID ${observationId} not found.`);
      }
    
      const {
        note: noteInput,
        identifier: identifierInput,
        encounterId: encounterIdInput,
        performerIds: performerIdsInput,
        subjectId: subjectIdInput, // Though less common to update subject, handle if passed
        ...restOfUpdates // These are fields that are mostly 1:1 or simple null->undefined
      } = updates;
    
      const workingUpdates: Partial<Observation> = {};
    
      // Handle fields from restOfUpdates, converting null to undefined
      for (const key in restOfUpdates) {
        if (Object.prototype.hasOwnProperty.call(restOfUpdates, key)) {
          const value = (restOfUpdates as any)[key];
          if (value === null) {
            (workingUpdates as any)[key] = undefined;
          } else if (value !== undefined) {
            (workingUpdates as any)[key] = value;
          }
        }
      }
    
      // Handle specific conversions for note
      if (typeof noteInput === 'string') {
        workingUpdates.note = [{ text: noteInput }];
      } else if (noteInput === null) {
        workingUpdates.note = undefined;
      } else if (noteInput !== undefined) { // If it was already Annotation[]
        workingUpdates.note = noteInput as any; // Cast if UpdateObservationArgs not updated
      }
    
      // Handle specific conversions for identifier
      if (identifierInput && typeof identifierInput === 'object') {
        workingUpdates.identifier = [identifierInput as Identifier];
      } else if (identifierInput === null) {
        workingUpdates.identifier = undefined;
      }
    
      // Handle subjectId to subject reference
      if (typeof subjectIdInput === 'string') {
        workingUpdates.subject = { reference: `Patient/${subjectIdInput}` };
      } else if (subjectIdInput === null) { // Allow clearing subject if necessary
        workingUpdates.subject = undefined;
      }
    
    
      // Handle encounterId to encounter reference
      if (typeof encounterIdInput === 'string') {
        workingUpdates.encounter = { reference: `Encounter/${encounterIdInput}` };
      } else if (encounterIdInput === null) {
        workingUpdates.encounter = undefined; // Clear the encounter
      }
    
      // Handle performerIds to performer references
      if (Array.isArray(performerIdsInput)) {
        workingUpdates.performer = performerIdsInput.map(id => ({ reference: `Practitioner/${id}` }));
      } else if (performerIdsInput === null) {
        workingUpdates.performer = undefined; // Clear performers
      }
    
      // value[x] exclusivity logic
      const valueFields: (keyof Observation)[] = [
        'valueQuantity', 'valueCodeableConcept', 'valueString', 'valueBoolean',
        'valueInteger', 'valueRange', 'valueRatio', 'valueSampledData',
        'valueTime', 'valueDateTime', 'valuePeriod'
      ];
    
      let valueKeyPresentInUpdates: keyof Observation | undefined;
      for (const key of valueFields) {
        // Check if the key (potentially a value[x] field) exists in the original `updates` object
        if ((updates as any)[key] !== undefined) {
          if (valueKeyPresentInUpdates) {
            // This indicates multiple value[x] fields were in the input `updates`.
            // The test "should throw error if updating with multiple value[x] types"
            // expects the Medplum server to reject this. So, we don't throw here,
            // but let Medplum handle it if workingUpdates still contains multiple.
          }
          valueKeyPresentInUpdates = key;
        }
      }
    
      // If a value[x] is being set in updates, ensure all other value[x] fields are cleared
      // from workingUpdates to ensure only one is sent to Medplum.
      if (valueKeyPresentInUpdates) {
        for (const key of valueFields) {
          if (key !== valueKeyPresentInUpdates) {
            (workingUpdates as any)[key] = undefined;
          }
        }
      }
    
      const updatedResource: Observation = {
        ...existingObservation,
        ...workingUpdates,
        resourceType: 'Observation', // Ensure resourceType is correctly maintained
        id: observationId,           // Ensure ID is correctly maintained
      };
    
      return medplum.updateResource(updatedResource);
    }
  • TypeScript interface defining the input arguments for updating an Observation, including all possible FHIR fields that can be updated.
    export interface UpdateObservationArgs {
      status?: Observation['status'];
      code?: CodeableConcept;
      subjectId?: string;
      encounterId?: string | null;
      effectiveDateTime?: string | null;
      effectivePeriod?: Period | null;
      issued?: string;
      performerIds?: string[] | null;
      valueQuantity?: Quantity;
      valueCodeableConcept?: CodeableConcept;
      valueString?: string;
      valueBoolean?: boolean;
      valueInteger?: number;
      valueRange?: Range;
      valueRatio?: Ratio;
      valueSampledData?: SampledData;
      valueTime?: string;
      valueDateTime?: string;
      valuePeriod?: Period;
      bodySite?: CodeableConcept | null;
      method?: CodeableConcept | null;
      referenceRange?: any[] | null;
      note?: string | null;
      interpretation?: CodeableConcept[] | null;
      identifier?: { system?: string; value: string } | null;
    }
  • MCP input schema definition for the 'updateObservation' tool, used by the MCP server for tool listing (listTools) and request validation (callTool).
    {
      name: "updateObservation",
      description: "Updates an existing observation. Requires the observation ID and the fields to update.",
      inputSchema: {
        type: "object",
        properties: {
          observationId: {
            type: "string",
            description: "The unique ID of the observation to update.",
          },
          status: {
            type: "string",
            description: "New status for the observation.",
            enum: ["registered", "preliminary", "final", "amended", "corrected", "cancelled"],
          },
          valueQuantity: {
            type: "number",
            description: "New numeric value of the observation. Optional.",
          },
          valueString: {
            type: "string",
            description: "New string value of the observation. Optional.",
          },
        },
        required: ["observationId"],
      },
    },
  • src/index.ts:970-970 (registration)
    Registration of the updateObservation handler function in the toolMapping object, which maps tool names to their implementations for execution in the MCP callTool handler.
    updateObservation,
  • src/index.ts:39-43 (registration)
    Import of the updateObservation function from observationUtils.ts into the main index.ts server file.
      createObservation,
      getObservationById,
      updateObservation,
      searchObservations,
    } from './tools/observationUtils.js';
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It states 'Requires the observation ID' which is already in the schema, and mentions 'fields to update' but doesn't disclose critical behavioral traits: whether this is a partial or full update, what permissions are needed, if changes are reversible, what happens to unspecified fields, or what the response contains. For a mutation tool with zero annotation coverage, this is inadequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately concise at two sentences, with the core purpose stated first. However, the second sentence ('Requires the observation ID and the fields to update') adds minimal value since this information is already in the schema, making it slightly redundant rather than purely efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an update/mutation tool with no annotations and no output schema, the description is incomplete. It doesn't explain what constitutes a successful update, what errors might occur, whether there are validation rules for field combinations, or what the tool returns. Given the complexity of updating medical observations and the lack of structured behavioral information, this leaves significant gaps for the agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents all 4 parameters with descriptions and enum values. The description adds no additional semantic meaning beyond what's in the schema - it merely restates that parameters include 'observation ID and the fields to update.' This meets the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Updates') and resource ('an existing observation'), making the purpose immediately understandable. It distinguishes from sibling 'createObservation' by specifying 'existing' observation, though it doesn't differentiate from other update tools like 'updateCondition' or 'updatePatient' beyond the resource type.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites (like needing an existing observation ID), when not to use it (e.g., for creating new observations), or how it differs from other update tools in the sibling list. The agent must infer usage from the tool name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

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/rkirkendall/medplum-mcp'

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