Use content instead of structuredContent in examples

Not all MCP clients support structuredContent in MCP tool results, so
examples now return data as JSON in content and parse it client-side.

Pattern used:
- Server: content: [{ type: "text", text: JSON.stringify(data) }]
- Client: JSON.parse(result.content!.filter(...).map(...).join(""))

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2025-12-05 16:18:02 -06:00
commit 61cd4ed219
2 changed files with 65 additions and 150 deletions

View file

@ -61,13 +61,6 @@ const GetScenarioDataInputSchema = z.object({
),
});
const GetScenarioDataOutputSchema = z.object({
templates: z.array(ScenarioTemplateSchema),
defaultInputs: ScenarioInputsSchema,
customProjections: z.array(MonthlyProjectionSchema).optional(),
customSummary: ScenarioSummarySchema.optional(),
});
// Types derived from schemas
type ScenarioInputs = z.infer<typeof ScenarioInputsSchema>;
type MonthlyProjection = z.infer<typeof MonthlyProjectionSchema>;
@ -247,37 +240,6 @@ const DEFAULT_INPUTS: ScenarioInputs = {
fixedCosts: 30000,
};
// ============================================================================
// Formatters for text output
// ============================================================================
function formatCurrency(value: number): string {
const absValue = Math.abs(value);
const sign = value < 0 ? "-" : "";
if (absValue >= 1_000_000) {
return `${sign}$${(absValue / 1_000_000).toFixed(2)}M`;
}
if (absValue >= 1_000) {
return `${sign}$${(absValue / 1_000).toFixed(1)}K`;
}
return `${sign}$${Math.round(absValue)}`;
}
function formatScenarioSummary(
summary: ScenarioSummary,
label: string,
): string {
return [
`${label}:`,
` Ending MRR: ${formatCurrency(summary.endingMRR)}`,
` ARR: ${formatCurrency(summary.arr)}`,
` Total Revenue: ${formatCurrency(summary.totalRevenue)}`,
` Total Profit: ${formatCurrency(summary.totalProfit)}`,
` MRR Growth: ${summary.mrrGrowthPct.toFixed(1)}%`,
` Break-even: ${summary.breakEvenMonth ? `Month ${summary.breakEvenMonth}` : "Not achieved"}`,
].join("\n");
}
// ============================================================================
// MCP Server
// ============================================================================
@ -298,7 +260,6 @@ const server = new McpServer({
description:
"Returns SaaS scenario templates and optionally computes custom projections for given inputs",
inputSchema: GetScenarioDataInputSchema.shape,
outputSchema: GetScenarioDataOutputSchema.shape,
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (args: {
@ -308,28 +269,18 @@ const server = new McpServer({
? calculateScenario(args.customInputs)
: undefined;
const text = [
"SaaS Scenario Modeler",
"=".repeat(40),
"",
"Available Templates:",
...SCENARIO_TEMPLATES.map(
(t) => ` ${t.icon} ${t.name}: ${t.description}`,
),
"",
customScenario
? formatScenarioSummary(customScenario.summary, "Custom Scenario")
: "Use customInputs parameter to compute projections for a specific scenario.",
].join("\n");
return {
content: [{ type: "text", text }],
structuredContent: {
templates: SCENARIO_TEMPLATES,
defaultInputs: DEFAULT_INPUTS,
customProjections: customScenario?.projections,
customSummary: customScenario?.summary,
},
content: [
{
type: "text",
text: JSON.stringify({
templates: SCENARIO_TEMPLATES,
defaultInputs: DEFAULT_INPUTS,
customProjections: customScenario?.projections,
customSummary: customScenario?.summary,
}),
},
],
};
},
);

View file

@ -24,11 +24,15 @@ interface CallToolResultData {
defaultInputs?: ScenarioInputs;
}
/** Extract templates and defaultInputs from tool result structuredContent */
/** Extract templates and defaultInputs from tool result content */
function extractResultData(result: CallToolResult): CallToolResultData {
if (!result.structuredContent) return {};
const { templates, defaultInputs } =
result.structuredContent as CallToolResultData;
const text = result
.content!.filter(
(c): c is { type: "text"; text: string } => c.type === "text",
)
.map((c) => c.text)
.join("");
const { templates, defaultInputs } = JSON.parse(text) as CallToolResultData;
return { templates, defaultInputs };
}