Stephen Gilmore.

MCP in an evening

Inspired by a conversation with an old friend, I decided I was overdue for checking out all the hype around Model Context Protocol (MCP). After brainstorming a few ideas, I picked one and got to work.

The project

Problem: Entering test cases to hire workers in Workday is common and tedious. You can run through approval steps in the UI, or fill out a template (which still requires looking up reference IDs piece-by-piece).

Goals:

  1. Automate this task
  2. Learn MCP

Solution: Build an MCP server that would let me use natural language to hire workers. “Can you help me hire a test worker like John Doe, but name them Jane Doe and give them a Sales job?” After a quick confirmation check, a tool would then enter the hire in Workday via integration APIs.

If you want to see some code… github.com/sglmr/Synthire

If you want to read more about the process and what I learned… keep reading!

Define the process

  1. Search: Call search_workers(name) to find a reference worker by name.
  2. Confirm: If more than one worker matches the search criteria, present each option’s name and details to the requester to choose from before moving on.
  3. Fetch: Once the worker is confirmed, call get_worker_template(employee_id) to pull all the details about the reference worker that will be needed to hire them.
  4. Generate identity: Call generate_synthetic_identity(country) using the reference worker’s country to get fresh details for the first name, last name, and email.
  5. Propose: Present a clear, complete summary of the proposed hire before calling anything that writes data. Ask for explicit confirmation to proceed and wait for their response. If they want changes, re-propose and confirm again. Don’t assume a tweak to one field is a green light for the rest.
  6. Execute: Call synthesize_test_hire(...) with the confirmed data fields. Report back the employee_id and wid on success. If there are any exceptions, report exceptions verbatim. These are the Workday validation messages like an unrecognized supervisory org or missing required field.

Note: This is called a “Human in the loop” or HITL. Where AI is doing most of the work and a human is approving or correcting the work at key checkpoints.

Gather data

I also gathered a bunch of data:

  1. AI help generated a CSV demographic file of fake workers and job details with all the data fields required to hire an employee.
  2. The Workday API request sample, response sample, specification, and WSDL for Get_Workers and Hire_Employee.

Define constraints

There were a few extra requirements and assumptions that I added:

  1. This is a hobby project. It’s not going to connect to a real Workday environment. Any authentication should be skipped/ignored.
  2. We will need a mock FastAPI server to handle accepting and responding to the Get_Workers and Hire_Employee API calls.
  3. The Hire_Employee API call should use the minimum required fields. (It could be extended later if needed).

agents.md and plan.md

Before getting to any code, I had Claude create an Agents.md file to make sure it understood the context for the project. Then I created a plan.md file to understand and guide the execution.

Build

The build was disappointingly delightfully uneventful. Claude knocked out a working prototype of everything in one go. I had to prompt it to add some logging and maybe one or two other minor features.

Lessons learned

What actually is MCP?

It’s a standard for connecting AI applications with external systems to extend their capabilities.

MCP Components

  • Host: The thing the human talks to. The “end-user application,” like Claude Desktop.
  • Client: Built into the host. One client per connected server, handling the connection and message routing.
  • Server: The thing you build. Declares the tools/resources/prompts and responds to the client’s requests.

MCP Primitives (types):

  1. Tools: Things like functions, search engines, calculators, etc.
  2. Resources: Data; things like local files or databases.
  3. Prompts: Commands for specific tasks. Ex. “/hire_like” to kick off the task instead of “hire someone like John Doe”.

stdout vs stderr

For local MCP, the server and client talk over stdio. The server’s responses go out over stdout. Log messages need to go to stderr instead, even routine ones that aren’t errors. Writing logs to stdout would mix them into the same stream the client is reading protocol messages from and corrupt the connection.

Tools vs Instructions:

Tools are single-purpose functions that a generative AI can use or request to extend its capabilities. In my project, I needed to string together a process that required multiple tools and a human in the loop (HITL). In this project, the instructions to string the separate tools together live in the Agents.md file, not inside a super-tool function or the protocol.

This means that an agent could be prompted in a way to use the tools differently or in unexpected ways.

Elicitation could go further — instead of relying on Agents.md to tell the agent to pause and ask, the tool itself could refuse to proceed without a structured, human-confirmed answer first.