feat: use structuredContent with outputSchema in example servers (#220)

* build app-with-deps

* make registerAppTool more interchangeable w/ server.registerTool

* feat: use structuredContent in all example servers

All servers now return structuredContent alongside JSON text content.
All mcp-apps now read structuredContent directly (no JSON.parse fallback).

Updated servers:
- basic-server-vanillajs
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- integration-server
- scenario-modeler-server
- system-monitor-server
- threejs-server
- video-resource-server
- wiki-explorer-server

* feat: add outputSchema to all example servers

Define output schemas for structuredContent validation:
- basic-server-vanillajs: { time: string }
- budget-allocator-server: BudgetDataResponseSchema
- cohort-heatmap-server: CohortDataSchema
- customer-segmentation-server: customers/segments schema
- integration-server: { time: string }
- scenario-modeler-server: templates/defaultInputs schema
- system-monitor-server: SystemStatsSchema
- threejs-server: { code, height }
- video-resource-server: { videoUri, description }
- wiki-explorer-server: { page, links, error }

* prettier:fix

* make registerAppTool more interchangeable w/ server.registerTool

* feat: use structuredContent in all example servers

All servers now return structuredContent alongside JSON text content.
All mcp-apps now read structuredContent directly (no JSON.parse fallback).

Updated servers:
- basic-server-vanillajs
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- integration-server
- scenario-modeler-server
- system-monitor-server
- threejs-server
- video-resource-server
- wiki-explorer-server

* feat: add outputSchema to all example servers

Define output schemas for structuredContent validation:
- basic-server-vanillajs: { time: string }
- budget-allocator-server: BudgetDataResponseSchema
- cohort-heatmap-server: CohortDataSchema
- customer-segmentation-server: customers/segments schema
- integration-server: { time: string }
- scenario-modeler-server: templates/defaultInputs schema
- system-monitor-server: SystemStatsSchema
- threejs-server: { code, height }
- video-resource-server: { videoUri, description }
- wiki-explorer-server: { page, links, error }

* prettier:fix

* revert: remove unrelated workflow, version, and arch changes

* chore: update package-lock.json

* refactor(basic-server-vanillajs): address PR review comments

- Simplify server.ts to return plain time string in text content (not JSON),
  matching the quickstart guide pattern
- Add defensive coding in mcp-app.ts with optional chaining and fallback
  to handle potential type mismatches gracefully

* refactor: restore rich text formatting for LLM-readable content

Restore the human-readable text formatting functions that were removed,
providing meaningful summaries in the text content while keeping
structuredContent for programmatic UI consumption:

- budget-allocator-server: formatBudgetSummary with config overview
- cohort-heatmap-server: formatCohortSummary with retention stats
- scenario-modeler-server: formatScenarioSummary with financial metrics
This commit is contained in:
Olivier Chafik 2026-01-12 14:25:16 +00:00 • committed by GitHub
commit 9f09932baf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -322,44 +322,36 @@ app.ontoolresult = (result) => {
};
function handleToolResultData(result: CallToolResult): void {
if (
result.isError ||
!result.content?.[0] ||
result.content[0].type !== "text"
) {
if (result.isError) {
console.error("Tool result error:", result);
return;
}
try {
const response: ToolResponse = JSON.parse(result.content[0].text);
const { page, links, error } = response;
const response = result.structuredContent as unknown as ToolResponse;
const { page, links, error } = response;
// Ensure the source node exists
addNode(page.url, page.title);
updateNodeTitle(page.url, page.title);
// Ensure the source node exists
addNode(page.url, page.title);
updateNodeTitle(page.url, page.title);
if (error) {
setNodeState(page.url, "error", error);
} else {
// Get source node position so new nodes appear nearby
const sourceNode = graphData.nodes.find((n) => n.url === page.url);
const sourcePos = sourceNode
? { x: sourceNode.x ?? 0, y: sourceNode.y ?? 0 }
: undefined;
if (error) {
setNodeState(page.url, "error", error);
} else {
// Get source node position so new nodes appear nearby
const sourceNode = graphData.nodes.find((n) => n.url === page.url);
const sourcePos = sourceNode
? { x: sourceNode.x ?? 0, y: sourceNode.y ?? 0 }
: undefined;
// Add all linked nodes and edges
for (const link of links) {
addNode(link.url, link.title, "default", sourcePos);
addEdge(page.url, link.url);
}
setNodeState(page.url, "expanded");
// Add all linked nodes and edges
for (const link of links) {
addNode(link.url, link.title, "default", sourcePos);
addEdge(page.url, link.url);
}
updateGraph();
} catch (e) {
console.error("Failed to parse tool result:", e);
setNodeState(page.url, "expanded");
}
updateGraph();
}
app.onerror = (err) => {