Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions examples/basic-server-react/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_URI_META_KEY } from "../../dist/src/app";

const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
Expand All @@ -30,14 +29,12 @@ const server = new McpServer({
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
inputSchema: {},
outputSchema: { time: z.string() },
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (): Promise<CallToolResult> => {
const time = new Date().toISOString();
return {
content: [{ type: "text", text: time }],
structuredContent: { time },
content: [{ type: "text", text: JSON.stringify({ time }) }],
};
},
);
Expand Down
8 changes: 6 additions & 2 deletions examples/basic-server-react/src/mcp-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ const log = {


function extractTime(callToolResult: CallToolResult): string {
const { time } = (callToolResult.structuredContent as { time?: string }) ?? {};
return time ?? "[ERROR]";
const text = callToolResult.content!
.filter((c): c is { type: "text"; text: string } => c.type === "text")
.map((c) => c.text)
.join("");
const { time } = JSON.parse(text) as { time: string };
return time;
}


Expand Down
5 changes: 1 addition & 4 deletions examples/basic-server-vanillajs/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import cors from "cors";
import express, { type Request, type Response } from "express";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { RESOURCE_URI_META_KEY } from "../../dist/src/app";

const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
Expand All @@ -30,14 +29,12 @@ const server = new McpServer({
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
inputSchema: {},
outputSchema: { time: z.string() },
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (): Promise<CallToolResult> => {
const time = new Date().toISOString();
return {
content: [{ type: "text", text: time }],
structuredContent: { time },
content: [{ type: "text", text: JSON.stringify({ time }) }],
};
},
);
Expand Down
8 changes: 6 additions & 2 deletions examples/basic-server-vanillajs/src/mcp-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ const log = {


function extractTime(result: CallToolResult): string {
const { time } = (result.structuredContent as { time?: string }) ?? {};
return time ?? "[ERROR]";
const text = result.content!
.filter((c): c is { type: "text"; text: string } => c.type === "text")
.map((c) => c.text)
.join("");
const { time } = JSON.parse(text) as { time: string };
return time;
}


Expand Down
28 changes: 1 addition & 27 deletions examples/budget-allocator-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,30 +221,6 @@ function generateHistory(
return months;
}

// ---------------------------------------------------------------------------
// Response Formatting
// ---------------------------------------------------------------------------

function formatBudgetSummary(data: BudgetDataResponse): string {
const lines: string[] = [
"Budget Allocator Configuration",
"==============================",
"",
`Default Budget: ${data.config.currencySymbol}${data.config.defaultBudget.toLocaleString()}`,
`Available Presets: ${data.config.presetBudgets.map((b) => `${data.config.currencySymbol}${b.toLocaleString()}`).join(", ")}`,
"",
"Categories:",
...data.config.categories.map(
(c) => ` - ${c.name}: ${c.defaultPercent}% default`,
),
"",
`Historical Data: ${data.analytics.history.length} months`,
`Benchmark Stages: ${data.analytics.stages.join(", ")}`,
`Default Stage: ${data.analytics.defaultStage}`,
];
return lines.join("\n");
}

// ---------------------------------------------------------------------------
// MCP Server Setup
// ---------------------------------------------------------------------------
Expand All @@ -263,7 +239,6 @@ server.registerTool(
description:
"Returns budget configuration with 24 months of historical allocations and industry benchmarks by company stage",
inputSchema: {},
outputSchema: BudgetDataResponseSchema.shape,
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (): Promise<CallToolResult> => {
Expand Down Expand Up @@ -292,10 +267,9 @@ server.registerTool(
content: [
{
type: "text",
text: formatBudgetSummary(response),
text: JSON.stringify(response),
},
],
structuredContent: response,
};
},
);
Expand Down
8 changes: 7 additions & 1 deletion examples/budget-allocator-server/src/mcp-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,13 @@ const app = new App({ name: "Budget Allocator", version: "1.0.0" });

app.ontoolresult = (result) => {
log.info("Received tool result:", result);
const data = result.structuredContent as unknown as BudgetDataResponse;
const text = result
.content!.filter(
(c): c is { type: "text"; text: string } => c.type === "text",
)
.map((c) => c.text)
.join("");
const data = JSON.parse(text) as BudgetDataResponse;
if (data?.config && data?.analytics) {
initializeUI(data.config, data.analytics);
}
Expand Down
15 changes: 1 addition & 14 deletions examples/cohort-heatmap-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,6 @@ function generateCohortData(
};
}

function formatCohortSummary(data: CohortData): string {
const avgRetention = data.cohorts
.flatMap((c) => c.cells)
.filter((cell) => cell.periodIndex > 0)
.reduce((sum, cell, _, arr) => sum + cell.retention / arr.length, 0);

return `Cohort Analysis: ${data.cohorts.length} cohorts, ${data.periods.length} periods
Average retention: ${(avgRetention * 100).toFixed(1)}%
Metric: ${data.metric}, Period: ${data.periodType}`;
}

const server = new McpServer({
name: "Cohort Heatmap Server",
version: "1.0.0",
Expand All @@ -176,7 +165,6 @@ const server = new McpServer({
description:
"Returns cohort retention heatmap data showing customer retention over time by signup month",
inputSchema: GetCohortDataInputSchema.shape,
outputSchema: CohortDataSchema.shape,
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async ({ metric, periodType, cohortCount, maxPeriods }) => {
Expand All @@ -188,8 +176,7 @@ const server = new McpServer({
);

return {
content: [{ type: "text", text: formatCohortSummary(data) }],
structuredContent: data,
content: [{ type: "text", text: JSON.stringify(data) }],
};
},
);
Expand Down
8 changes: 7 additions & 1 deletion examples/cohort-heatmap-server/src/mcp-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ function CohortHeatmapInner({ app }: { app: App }) {
maxPeriods: 12,
},
});
setData(result.structuredContent as unknown as CohortData);
const text = result
.content!.filter(
(c): c is { type: "text"; text: string } => c.type === "text",
)
.map((c) => c.text)
.join("");
setData(JSON.parse(text) as CohortData);
} catch (e) {
console.error("Failed to fetch cohort data:", e);
} finally {
Expand Down
27 changes: 1 addition & 26 deletions examples/customer-segmentation-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,6 @@ const GetCustomerDataInputSchema = z.object({
.describe("Filter by segment (default: All)"),
});

const CustomerSchema = z.object({
id: z.string(),
name: z.string(),
segment: z.string(),
annualRevenue: z.number(),
employeeCount: z.number(),
accountAge: z.number(),
engagementScore: z.number(),
supportTickets: z.number(),
nps: z.number(),
});

const SegmentSummarySchema = z.object({
name: z.string(),
count: z.number(),
color: z.string(),
});

const GetCustomerDataOutputSchema = z.object({
customers: z.array(CustomerSchema),
segments: z.array(SegmentSummarySchema),
});

// Cache generated data for session consistency
let cachedCustomers: Customer[] | null = null;
let cachedSegments: SegmentSummary[] | null = null;
Expand Down Expand Up @@ -92,15 +69,13 @@ const server = new McpServer({
description:
"Returns customer data with segment information for visualization. Optionally filter by segment.",
inputSchema: GetCustomerDataInputSchema.shape,
outputSchema: GetCustomerDataOutputSchema.shape,
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async ({ segment }): Promise<CallToolResult> => {
const data = getCustomerData(segment);

return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
structuredContent: data,
content: [{ type: "text", text: JSON.stringify(data) }],
};
},
);
Expand Down
8 changes: 7 additions & 1 deletion examples/customer-segmentation-server/src/mcp-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,13 @@ async function fetchData(): Promise<void> {
arguments: {},
});

const data = result.structuredContent as unknown as {
const text = result
.content!.filter(
(c): c is { type: "text"; text: string } => c.type === "text",
)
.map((c) => c.text)
.join("");
const data = JSON.parse(text) as {
customers: Customer[];
segments: SegmentSummary[];
};
Expand Down
71 changes: 11 additions & 60 deletions examples/scenario-modeler-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -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
// ============================================================================
Expand All @@ -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: {
Expand All @@ -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,
}),
},
],
};
},
);
Expand Down
12 changes: 8 additions & 4 deletions examples/scenario-modeler-server/src/mcp-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

Expand Down
4 changes: 1 addition & 3 deletions examples/system-monitor-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ const server = new McpServer({
description:
"Returns current system statistics including per-core CPU usage, memory, and system info.",
inputSchema: {},
outputSchema: SystemStatsSchema.shape,
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (): Promise<CallToolResult> => {
Expand All @@ -148,8 +147,7 @@ const server = new McpServer({
};

return {
content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
structuredContent: stats,
content: [{ type: "text", text: JSON.stringify(stats) }],
};
},
);
Expand Down
Loading