Welcome to the final chapter of our tutorial!
In the previous chapter, Content Persistence Strategy, we learned how to handle complex files (like images or PDFs) by saving them to disk. We successfully retrieved the data, but right now, that data is just sitting inside a computer variable.
The user hasn't seen anything yet.
Imagine a chef (the Tool) who has cooked a delicious meal (the Data). However, the chef stays in the kitchen. If the waiter doesn't pick up the food and plate it nicely, the customer sits at an empty table.
In software, User Interface (UI) Rendering is the waiter. It is the bridge between the code logic and the human eye.
The Use Case:
When the AI decides to "Read a file," the user should see a clean message in their terminal saying: "Read resource 'config.json' from server 'Backend'."
>
When the tool finishes, the user should see the result (the JSON content) formatted neatly, not a jumbled mess of text.
We solve this by creating a View layer using React Components. This separates the "heavy lifting" (logic) from the "pretty presentation" (UI).
You might know React as a tool for building websites. However, we use a special library called Ink that allows us to build React components that render text inside a command-line terminal. It lets us use boxes, colors, and layouts just like HTML, but for the console.
renderToolUseMessage)This function defines what the user sees before the tool finishes running. It serves as a confirmation: "I am currently doing X with parameters Y."
renderToolResultMessage)
This function defines what the user sees after the tool finishes. It takes the final data (the Output schema we defined in Chapter 2) and formats it to be human-readable.
We write our UI code in a file named UI.tsx, typically located in the same folder as our tool logic.
First, we provide a simple internal name. This is often used for logging or debugging in the UI.
// UI.tsx
export function userFacingName(): string {
return 'readMcpResource';
}
Explanation: This is a simple helper string. It acts as a label for the UI system.
When the AI starts the tool, we want to show a summary sentence.
// UI.tsx
export function renderToolUseMessage(input) {
// Safety check: Don't print if data is missing
if (!input.uri || !input.server) {
return null;
}
// A friendly, human-readable sentence
return `Read resource "${input.uri}" from server "${input.server}"`;
}
Explanation:
input (the arguments the AI chose).Now we need to display the result. First, we handle the edge case where nothing came back.
// UI.tsx
export function renderToolResultMessage(output) {
// Check if content is missing or empty
if (!output || !output.contents || output.contents.length === 0) {
return (
<Box justifyContent="space-between" width="100%">
<Text dimColor>(No content)</Text>
</Box>
);
}
// ... (code continues)
Explanation:
contents is empty.<Box> acts like a <div> in HTML.<Text dimColor> makes the text gray/faint, indicating it's not very important.If we do have content, we want to display it. Since our result is structured data, showing it as formatted JSON is usually best.
// ... (inside renderToolResultMessage)
// Convert the object to a pretty string with indentation
const formattedOutput = jsonStringify(output, null, 2);
// Use a pre-built component to display the block
return <OutputLine content={formattedOutput} verbose={verbose} />;
}
Explanation:
jsonStringify(..., 2): This adds indentation (spaces) so the JSON isn't one long unreadable line.<OutputLine />: This is a custom component in our project that handles syntax highlighting and scrolling for long text.How does the application know when to call these functions?
The application follows an MVC (Model-View-Controller) pattern.
call function from Chapter 1).UI.tsx file).
The UI.tsx file acts as a plugin. The main application imports this file. When the tool lifecycle events occur (Start, Success, Failure), the application looks for these specific function names (renderToolUseMessage, renderToolResultMessage).
If these functions didn't exist, the application would likely default to showing the raw, ugly JSON string for everything. By providing UI.tsx, we override that default behavior with a "Premium Experience."
You might notice imports like Box and Text from ../../ink.js.
import { Box, Text } from '../../ink.js';
These are wrappers around the Ink library.
<Box>: Handles layout (flexbox). You can set margins, padding, and width.<Text>: Handles styling. You can set colors (green, red, dim) and styles (bold, underline).This allows us to create a command-line interface that feels as polished as a web page.
Congratulations! You have completed the ReadMcpResourceTool tutorial series.
Let's recap what you have built:
You now possess a fully functional tool that empowers an AI to read files from any connected server, handle the data safely, and present it beautifully. You have mastered the full lifecycle of an MCP Tool!
Generated by Code IQ