AI Engineer

MCP Apps: Give the Model Data, Give the User a UI — Dustin Mihalik, Indeed

1750 summary words 8 min summary Watch video

Start with the signal

8 min read

Summary

At-a-Glance

  • Verdict: Watch fully
  • Core thesis: Effective MCP Apps should treat UI as a rendering layer over model-visible, composable data tools—not as a black-box website embedded in chat.
  • Why it matters: This is a practical architecture pattern for preserving agent reasoning, multi-step exploration, and reliable follow-up interactions while adding rich UI inside Claude or ChatGPT.
  • Best use: Use it as a design review checklist for any MCP/App SDK integration that combines agent workflows with interactive results, especially search, recommendations, commerce, maps, or operational dashboards.

Executive Summary

Dustin Mihalik describes lessons from Indeed's implementation of MCP Apps across Claude, ChatGPT, and Indeed's internal CareerScout agent. His central warning is that embedding an existing web experience or calling opaque UI APIs creates a black box: the user can see results, but the model cannot reason about what was rendered, answer questions about it, or reliably continue the task.

The first operating rule is to return every user-visible item as structured content to the model as well as rendering it in the app UI. The second is to propagate meaningful UI interactions—such as opening a job's details—back into model context. Without that, requests such as "summarize this job" or "write a cover letter for it" fail because the model does not know which result the user selected or what details they saw.

The most important architectural recommendation is to separate data processing from UI rendering. A single search-and-render tool discourages the model from conducting the multi-search, filtering, comparison, and selection work users actually delegate to an agent. Instead, Indeed uses data-only tools such as job search, followed by an explicit rendering tool that receives a selected list of job IDs.

This separation lets the model explore broadly, choose the best subset, and render only the final answer rather than producing many redundant carousels. It also allows render tools to carry model-generated rationale or highlights, turning a generic widget into a personalized recommendation interface. The talk is brief but unusually concrete on MCP App failure modes and tool decomposition.

Key Takeaways

  • Claim: Every piece of information shown in an MCP App UI must also be supplied to the model as structured data. | Evidence: Mihalik says a naive implementation calls existing APIs to populate HTML/UI, leaving the model unable to see the displayed jobs; follow-ups such as "tell me about the first result" or "rank these companies" then become impossible. MCP Apps support returning both structured content and a resource URI for the HTML. | Implication: Treat the structured tool result as the authoritative semantic state and the widget as a presentation of that state; do not make client-side UI state the only source of truth. | Caveat: Returning both channels creates a synchronization obligation: when the UI/API gains a field, the corresponding model-visible data must be updated as well.
  • Claim: Tool descriptions must explicitly tell the model when results are already rendered, or the model will redundantly describe the same output in text. | Evidence: After a UI is added, the model still attempts its normal text response alongside the displayed result. Mihalik reports that wording such as "results were automatically displayed to the user as UI components" near the top of a tool description resolves many cases. | Implication: Descriptions are not documentation only; use them as behavioral controls for the division of labor between conversational narration and rendered UI.
  • Claim: Interactive UI events need to update model context, just as initial rendered data does. | Evidence: If a user opens a job-details modal after receiving ten results, the model does not inherently know which job was clicked or what dynamically loaded details are visible. The MCP App Spec's update model context method accepts a string; the cited shopping-cart example passes cart items and total so users can ask about their current cart. | Implication: Define an explicit event-to-context contract for selections, expanded details, filters, cart changes, approvals, and other UI state that may become the subject of a follow-up request. | Caveat: The MCP Apps mechanism described uses one string for model context, so applications tracking multiple events must append and manage accumulated state deliberately.
  • Claim: Separate data-processing tools from UI-rendering tools; this is the speaker's highest-priority design rule. | Evidence: For a complex job request involving multiple cities, relocation, compensation, and excluded industries, a text MCP workflow can run 10–15 searches, filter results, and create a final table. If the search tool automatically renders a carousel, Claude tends to call it once and infer that the work is already done, rather than conducting the deeper investigation. | Implication: Keep exploration and reasoning unconstrained by presentation. Invoke rendering only after the agent has gathered, compared, filtered, and selected the results worth surfacing.
  • Claim: A render tool should accept a curated reference set rather than owning the search process. | Evidence: Indeed changes from a single search-jobs tool that renders immediately to a data-only search-jobs tool plus render-jobs-widget, which takes a list of job IDs. The model can retrieve roughly 100 jobs, filter them, and display the five it considers most relevant. | Implication: For agentic systems, build render endpoints around IDs or validated references from prior data tools, enabling a clean handoff from retrieval and reasoning to a controlled UI.
  • Claim: Small, composable search and render tools give models more flexibility without requiring overloaded tool descriptions. | Evidence: Mihalik proposes two or three specialized job-search tools with one or two render tools, such as rendering a list versus highlighting one job. Tool descriptions can simply state which data tool must be called before rendering and the required input format. | Implication: Decompose an MCP integration by task stage and output mode rather than exposing a monolithic "do everything and show it" endpoint; this improves model choice and makes evaluations more targeted.
  • Claim: Rendering can preserve and expose the model's judgment, not merely display retrieved records. | Evidence: The proposed render-jobs widget can take an ID plus the model's reason it is a strong fit, or an ID plus a highlighted portion of the job description. Mihalik extends the same pattern to e-commerce choices and maps built from selected addresses. | Implication: Design rendering schemas to carry concise agent rationale, fit explanations, highlights, or annotations, while keeping the factual underlying data separately available for grounding and follow-up questions.

Detailed Brief

Product and platform constraints that motivated MCP Apps

  • Claims: Plain text MCP responses can be functional for job search but provide limited brand control and weak conversion affordances.; External links are not reliably produced by chat models and may be disfavored because host assistants prefer users to remain inside their own environments.; A native widget can provide product-controlled actions and in-context detail views without forcing users out of the assistant.
  • Evidence: Indeed's initial text response for a barista search listed jobs in Austin and nearby suburbs but had no Indeed branding.; Mihalik says Indeed spent a "ridiculous number of hours" in evaluations to make Claude consistently link to jobs.; The MCP/App SDK interface supports an Apply button, prioritized fields, and a View Details action that opens a modal inside the host environment.
  • Caveats: Rich UI improves control and conversion, but it does not automatically create an agent-capable experience; opaque rendered content still breaks model awareness.
  • Implications: Evaluate embedded-app success on task completion and follow-up coherence, not on whether a web component can be displayed in a chat host.; Use native UI controls for high-value conversion steps while preserving enough structured state for the agent to explain and continue the workflow.

Generalization beyond job search

  • Claims: The data-versus-rendering split applies wherever an agent needs to explore a large option set before presenting a compact visual answer.; The model should be allowed to determine the final display set after reasoning, rather than be forced to render each intermediate tool result.
  • Evidence: Mihalik cites e-commerce as a comparable use case and maps where the model identifies five addresses before passing them to a rendering component.; The talk contrasts a single immediately rendered result set with a final curated set selected after broad search and filtering.
  • Implications: Apply this architecture to operations consoles, procurement workflows, portfolio screening, incident triage, and other systems where agents investigate many records but operators need a focused final UI.

Notable Concepts & Terms

  • MCP Apps: An MCP extension pattern for pairing tool results with a rendered application UI inside an AI host; the talk focuses on making that UI semantically visible to the model.
  • Structured content: The model-readable data returned with a tool call; it must correspond to what the user sees in the widget.
  • Resource URI: The reference to the HTML/UI resource used to render an MCP App alongside its structured content.
  • update model context: MCP App mechanism for communicating UI-driven state, such as a selected job or shopping-cart contents, back to the model.
  • Data processing versus UI rendering: The core architecture principle: let the model use ordinary data tools for iterative reasoning, then call dedicated UI tools only to present the selected output.
  • Render tool: A dedicated widget invocation, such as render-jobs-widget, that receives validated references like job IDs and optionally model-generated annotations.
  • CareerScout: Indeed's internal agent for job seekers, one of the implementation contexts informing the speaker's lessons.

Operator Notes / Why Ken Should Care

  • Add a review gate to MCP/App SDK designs: identify every field the widget displays and verify it is present in the tool's structured model output.
  • Create a UI-event context schema for agent systems, covering selection, expansion, filter changes, approval state, and user edits; decide how accumulated state is compacted rather than indefinitely appended.
  • Split any combined retrieve-and-render tool into data-only retrieval/action tools and one or more presentation tools before evaluating agent performance.
  • Test complex, multi-step prompts—not single-query demos—to detect whether automatic rendering prematurely stops the model from conducting comparisons, searches, or filtering.
  • Add evaluation cases for redundant narration, unavailable selected-item context, link/action reliability, and whether rendered recommendation rationale remains grounded in retrieved data.
  • For high-value workflows, constrain render inputs to IDs or references produced by prior tools rather than accepting arbitrary unverified display payloads.

Source/Metadata

  • Title: MCP Apps: Give the Model Data, Give the User a UI — Dustin Mihalik, Indeed
  • Transcript words: 2372
  • Duration seconds: 934
  • Timestamp note: No timestamps or chapters were present in the supplied transcript.
Full transcript 2299 words · 11 min read
0:00

Hey everyone, I'm Dustin Mahalik.

0:13

I work at Indeed. We're the number one job site in the world. And I have to apologize for my voice. I'm recovering from a cold that I had last week. Yeah, so at Indeed we build Job Search and I also work on a team that does AI platform. And I work on AI guardrails and gateways and compliance stuff. Occasionally my team gets cool projects to work on because we have relationships with the vendors. MCP apps is one of those and MCP connectors. So this is practical MCP apps. They gave a great introduction to MCP apps. This is lessons from the trenches which is what did we learn in building MCP and MCP apps for Claude, ChatGPT, and our own internal CareerScout

1:09

which is our agent that we have for job seekers. So this is the chat based interface. So a lot of my examples are going to be job search. I have a lot of screenshots of job searches. So this is a job search which is basically, I'm looking for a barista in Austin and this is a text based response. And this works pretty well. As we discussed, there's no branding here. There's no Indeed branding. Claude decided to say these are some jobs in Austin. These are some jobs in some suburbs of Austin. That's cool. That's probably good for the user. There may be some limitations of what we can do for branding or how we can control things.

1:56

And you'd actually be surprised unless you've tried to do this yourself that it's really hard to get Claude or ChatGPT to link to things because they don't want you to leave their environment. It makes complete sense. But if you get back here's five jobs that are somewhere on the internet without any links, that's a terrible user experience. So it took us a ridiculous number of hours in evals to make sure that Claude would consistently link to things. So with MCP apps and with apps SDK, we can control that. We can decide that we've got an apply button. We can decide what stuff is important that we want to highlight at the top.

2:44

We can provide a link to view details so that when you click it, you get a pop-up, a modal that has all the job details so you don't have to leave the environment. So it's a win-win for both companies. So MCP apps is really good. But if you're thinking about, hey, I want to build an MCP app or I want to take my website, I want to put it in ChatGPT or Claude, it's not quite as easy as dragging and dropping into a chat interface. You really want to think about how you are representing the data, how you're making it available to the user. So if you do a very naive thing, which is you still call your existing APIs for loading data,

3:32

then it basically becomes a black box to the model, right? So you say, hey, I want to do something. The model says, cool, I'll call a tool. The tool says, okay, I'm going to show some stuff, but the model has no idea what you're displaying. So there's all these follow-up questions, like tell me about the first result or please rank this list of companies. The model has no idea what data is being displayed. So the very first rule for me building MCP apps, anything that you show to the user also needs to be provided as data to the model. I think this makes sense, but I've definitely seen some MCP apps where they just use it to inject some HTML on the page

4:18

and then call some APIs, and that just makes a big black box for the model. So this is really easy to do using MCP spec and the MCP app spec. This structured content, this is what you would already be returning if you were doing just text-based MCP. And then this resource URI, that points to where the HTML is. You need to return both of these and you need to keep them in sync, right? If you add something new to the API, you make sure you add that same data back. So kind of the next step that once you do that, you'll find is that now you've provided data to the model and you provided this black box that it has no idea about, it's still going to try and describe the output.

4:55

It's still going to try and take the output and describe it as it normally would. So you end up with here's your display and then here's the model doing basically the same thing that it would normally do. So what you need to do is you need to update your description in order to tell it that you're going to be displaying stuff in your MCP app. That way you get this nice, here's a list of, here's a little summary of things and the results are shown above rather than it trying to do a whole text-based display. You'll end up with a little bit of a battle between what gets displayed in UI and what gets displayed by the model. You can try and steer that with descriptions.

5:29

So even something as simple as results were automatically displayed to the user as UI components at the top of your description, your tool description, that covers quite a bit of the cases. So that's one of the next things that you're going to want to do once you're providing both data and API access. The next thing is there's these interactable pieces, right? There's the apply button, there's a view details button, which pops up a big job description. This is the same case where, as you interact with those, the model's not going to know necessarily what you're looking at. So you click view details, you get a big modal that's here's everything about the job.

6:20

There's a whole bunch of questions that the user could ask. Write a cover letter for this job. Summarize this job description. It has no idea because you've loaded that data in either via API or you loaded it in dynamically. You've returned 10 jobs and user clicks on one. The model has no idea which one you clicked on. So any information about user interactions, you also need to provide to the model. And once again, MCP App Spec has a pretty easy way to handle it. There's this update model context method, which lets you pass in a string. So for MCP Apps, there's a single string. So if you want to track multiple events over time,

7:06

you have to append multiple things to the string. But this is an example from the MCP Apps documentation, where basically this is a shopping cart application, and they add the total cost and all the items that are on the shopping cart. So the user can ask for information about the items that are in their shopping cart. So these two things give you an app that the model can see what's going on. But it doesn't necessarily make a really good MCP App yet. Because it gives you some UI that looks like what you want. But the thing that I usually do, I don't give Claude my easy problems to solve. I give Claude my really hard problems to solve, right?

7:56

If I just wanted to do one search, I would go to the web and do one search.

8:06

I want to do a whole bunch of searches. This is out of date because there's no Sonnet 5 here yet. But this is a screenshot from two days ago. But yeah, so this job search is, hey, I'm looking for this title. I'm willing to relocate. So I want to search across a whole bunch of different cities. I'm looking for the highest paying options. So I want you to cherry pick a few out of there. There's some industries that I absolutely don't want to work in. Text-based MCP does really well. We all see this, right? Claude will do 10 different searches, 15 different searches. It'll filter, it'll pull out all the individual pieces. And then it gives you a nice table at the bottom,

8:59

which is super nice. But with the MCP app that we were just discussing, you know, we said, hey, my results are going to be displayed in this UI. Claude will call it once, and then it'll be like, oh, I guess the results are already displayed. I'm not going to do a deep dive, right? You as a user are not going to want 10 different carousels. And Claude will also notice that it's already been displaying some stuff, and it won't call to show 10 different carousels. And so what you really want to do, and this is rule three, this supersedes all the other rules, which is basically you want to separate your data processing from your UI rendering.

9:48

And this particular wording I stole from OpenAI's apps SDK documentation, there's a couple places where they say this. But basically, you want to separate your data processing from your UI rendering. So the job search that we had, we're just going to have that be a standard text-based MCP application. Claude can call that as many times as it wants. And then we have a render tool that either you could pass, you could have the model basically pass all the data that it wants to render in, or you can do a reference to it.

10:26

So in our particular case, you know, we had search jobs. Now we have a search jobs that doesn't return in the UI. And we have a render jobs widget that takes a list of IDs. And so that list of IDs can be Claude can do a search, it can get 100 different jobs that it cares about. It can filter those, it could find five that it cares about, and it can show those five to the user. Now, as you make these render calls, you need to update the descriptions to say, where they get the data, what format the data should be. But it's a fairly easy mechanism to update your tool description to say, you need to always call one of these three tools first

11:14

in order to get the data that you're going to be using for rendering. So search jobs, this is a pretty good example of where we want to split data from rendering. I think there's a ton of other examples. In most industries, you can come up with, where do I want to split? I want the model to be able to explore this data, and then I want it to turn around and choose to render it, right? There's examples of e-commerce, right? A bunch of e-commerce options. If you've got a map, maybe you want to come up with five different addresses, and then you pass in addresses. The other thing that you can do is you can let the model be a lot more creative.

12:11

One of the things that we've seen in some of this text-based stuff is that the model will say, this is a reason why I picked this one, or this is a reason why this is really good. And so potentially we can add something to the render jobs widget where you say, give us an ID and a reason why you think that this is a good fit. Or give us an ID and highlight a section of the job description that is really good. So you can get really creative with your render tools to be able to have the model inject some extra character into those so that you've got a much better experience for the user. So the key takeaways, right, when building MCP apps, you want to

12:50

focus on the data before you focus on the UI, which sounds opposite of how we were thinking about it, of hey, there's all these MCP apps. It's how I put UI into ChatGPT. I think if you want to really have a good MCP apps experience, you need to look and see what data do I want to give to the model, what data do I want it to be able to do, and then rendering is a side effect of that, or it's a result of the model exploring the data. And then I think small composable tools, right, so basically, maybe there's two or three different ways you can search for jobs. We can build two or three different search tools, and then there's one render tool.

13:46

Or maybe there's two render tools. There's one that's render a list of jobs, one that's highlight one particular job. So you can build a bunch of much smaller tools. The descriptions can be fairly simple, so you don't overload the model, but it gives the model flexibility about how it wants to explore the data and how it wants to render the tools. So that's basically my talk. I'm a few minutes fast. But I don't have a booth that I'm going to hang out in, but I'll hang out in the hall if anyone has any questions. And I am either my last name or my first initial last name on most social media platforms. Thank you. highlight one particular job.

14:39

So you can build a bunch of much smaller tools. The descriptions can be fairly simple, so you don't overload the model, but it gives the model flexibility about how it wants to explore the data and how it wants to render the tools. So that's basically my talk. I'm a few minutes fast. But I don't have a booth that I'm going to hang out in, but I'll hang out in the hall if anyone has any questions. And I am either my last name or my first initial last name on most social media platforms. Thank you. . . . . . . .

15:21

!

Reading tools

Type to find a passage

Appearance
Ask this transcript

Add a note