Welcome to the final chapter of our beginner tutorial!
In the previous chapter, the Result Visualization Engine, we learned how to display images and progress bars. But there is one final, annoying problem we need to fix to make our chat truly human-friendly.
Tools and APIs communicate using JSON (JavaScript Object Notation). It is great for computers, but rigid for humans.
Imagine you ask your AI to "Summarize this file." The tool might return this valid JSON:
{
"status": "success",
"code": 200,
"content": "The file contains a list of shopping items...",
"metadata": { "time": "12ms" }
}
If we display this raw JSON in the chat, it looks like a robotic log file. You have to mentally ignore "status": "success" and hunt for the actual text inside "content".
We need a system that acts like a Translator.
This system uses heuristics (smart guesses) to decide when to strip away the JSON syntax and when to keep it.
Our Unwrapper tries to clean up the mess using two main strategies:
If a JSON object contains one really long string (like a message body) and a bunch of tiny metadata, the Unwrapper assumes you only care about the text.
{"id": 1, "msg": "This is a very long and important message that..."}msg is huge. The field id is tiny. I'll just show the message."If the JSON is a simple list of settings or data (Key: Value), the Unwrapper strips the brackets and quotes to make a clean list.
{"temperature": 72, "city": "Paris", "cloudy": true} temperature: 72
city: Paris
cloudy: true
Let's visualize the decision process. When a tool returns text, we don't just print it. We pass it through our Unwrapper functions.
Let's look at UI.tsx to see how these heuristics are written in code.
We look for a "Dominant" string. We define dominant as "Longer than 200 characters" OR "Contains newlines (like a paragraph)."
// File: UI.tsx
export function tryUnwrapTextPayload(content: string) {
const entries = parseJsonEntries(content); // Convert JSON string to Object
let body = null;
for (const [key, value] of entries) {
if (typeof value === 'string') {
// Is this string "Dominant"? (Long or multi-line)
const isDominant = value.length > 200 || (value.includes('\n') && value.length > 50);
if (isDominant) {
body = value; // We found our main content!
}
}
}
return body ? { body } : null;
}
Explanation:
If there is no dominant text, we check if the object is "flat" (simple key-value pairs).
// File: UI.tsx
export function tryFlattenJson(content: string) {
const entries = parseJsonEntries(content);
const result = [];
for (const [key, value] of entries) {
// We only want simple values: Strings, Numbers, or Booleans
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
result.push([key, String(value)]);
} else {
// If we find a complex nested object, abort! It's not flat.
return null;
}
}
return result;
}
Explanation:
123, "Hello", true), we add it to our list.{"sub_object": ...}), we assume this data is too complex to flatten, so we return null and let the fallback system handle it.Now we use these helper functions in our main render component.
// File: UI.tsx (Simplified)
function MCPTextOutput({ content }) {
// Strategy 1: Try to just show the text
const unwrapped = tryUnwrapTextPayload(content);
if (unwrapped) {
return <Text>{unwrapped.body}</Text>;
}
// Strategy 2: Try to show a clean list
const flat = tryFlattenJson(content);
if (flat) {
return flat.map(([key, val]) => (
<Text>{key}: {val}</Text>
));
}
// Fallback: Just show the raw content (safest option)
return <Text>{content}</Text>;
}
This logic ensures the user always gets the cleanest possible view, but if the data is weird or complex, we play it safe and show the raw data so nothing is lost.
Congratulations! You have completed the MCPTool architecture tutorial.
Let's recap what we built:
By combining these four layers, MCPTool creates a seamless bridge between raw code execution and a smooth, human-friendly chat interface.
You are now ready to explore the codebase and build your own tools! Happy coding!
Generated by Code IQ