Skip to content

Commit dc7ea63

Browse files
committed
Use Express-style API for MCP server
Introduced a few months ago in v1.3.0 of the MCP SDK. modelcontextprotocol/typescript-sdk#117
1 parent 5db99f1 commit dc7ea63

12 files changed

+106
-135
lines changed

src/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { GridServer } from "./server/gridServer.js";
1+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22

3-
const server = new GridServer();
4-
server.start().catch((error) => {
5-
console.error("Fatal error:", error);
6-
process.exit(1);
7-
});
3+
import { gridServer } from "./server/gridServer.js";
4+
5+
// Receive messages on stdin, send messages on stdout.
6+
const transport = new StdioServerTransport();
7+
await gridServer.connect(transport);

src/schemas/queryWorkbookInputSchema.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { z } from "zod";
22

3-
export const QueryWorkbookInputSchema = z.object({
3+
/** Raw Zod shape for `query_workbook` request input. Used with `server.tool()`. */
4+
export const queryWorkbookInputSchema = {
45
workbookId: z.string(),
56
read: z.array(z.string()),
67
apply: z
@@ -11,4 +12,11 @@ export const QueryWorkbookInputSchema = z.object({
1112
}),
1213
)
1314
.optional(),
14-
});
15+
};
16+
17+
const queryWorkbookInputSchemaZodObject = z.object(queryWorkbookInputSchema);
18+
/**
19+
* Complete Zod object for `query_workbook` request input. Used as a type for server tool callback
20+
* functions.
21+
*/
22+
export type QueryWorkbookInputSchema = z.infer<typeof queryWorkbookInputSchemaZodObject>;

src/schemas/workbookChartInputSchema.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { z } from "zod";
22

33
const supportedChartTypes = ["line", "column"] as const;
44

5-
export const workbookChartInputSchema = z.object({
5+
/** Raw Zod shape for `get_workbook_chart` request input. Used with `server.tool()`. */
6+
export const workbookChartInputSchema = {
67
id: z.string().describe("Unique workbook id"),
78
type: z.enum(supportedChartTypes).describe("Type of chart to render"),
89
data: z
@@ -20,4 +21,11 @@ export const workbookChartInputSchema = z.object({
2021
.optional()
2122
.describe("Cell reference to use as the chart's title. Can also be plain text.")
2223
.default(""),
23-
});
24+
};
25+
26+
const workbookChartInputSchemaZodObject = z.object(workbookChartInputSchema);
27+
/**
28+
* Complete Zod object for `get_workbook_chart` request input. Used as a type for server tool
29+
* callback functions.
30+
*/
31+
export type WorkbookChartInputSchema = z.infer<typeof workbookChartInputSchemaZodObject>;

src/schemas/workbookChartURLInputSchema.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { z } from "zod";
33
const SupportedChartFormats = z.enum(["png", "svg"]);
44
const SupportedChartTypes = z.enum(["line", "column"]);
55

6-
export const workbookChartURLInputSchema = z.object({
6+
/** Raw Zod shape for `get_workbook_chart_url` request input. Used with `server.tool()`. */
7+
export const workbookChartURLInputSchema = {
78
// Required path parameter
89
id: z.string().describe("Unique workbook id"),
910

@@ -22,4 +23,11 @@ export const workbookChartURLInputSchema = z.object({
2223
.string()
2324
.optional()
2425
.describe("Cell reference to use as the chart's title. Can also be plain text."),
25-
});
26+
};
27+
28+
const workbookChartURLInputSchemaZodObject = z.object(workbookChartURLInputSchema);
29+
/**
30+
* Complete Zod object for `get_workbook_chart_url` request input. Used as a type for server tool
31+
* callback functions.
32+
*/
33+
export type WorkbookChartURLInputSchema = z.infer<typeof workbookChartURLInputSchemaZodObject>;

src/server/gridServer.ts

Lines changed: 32 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,44 @@
1-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
3+
import { queryWorkbookInputSchema } from "../schemas/queryWorkbookInputSchema.js";
4+
import { workbookChartInputSchema } from "../schemas/workbookChartInputSchema.js";
5+
import { workbookChartURLInputSchema } from "../schemas/workbookChartURLInputSchema.js";
46
import {
57
handleGetWorkbookChart,
68
handleGetWorkbookChartURL,
79
handleQueryWorkbook,
8-
} from "./handlers/index.js";
9-
import { getWorkbookChart, getWorkbookChartURL, queryWorkbook } from "./tools/index.js";
10+
} from "../server/handlers/index.js";
1011

1112
/**
1213
* MCP server for the GRID API.
1314
*
1415
* <https://modelcontextprotocol.io/docs/concepts/architecture>
1516
*/
16-
export class GridServer {
17-
private server: Server;
18-
19-
constructor() {
20-
this.server = new Server(
21-
{
22-
name: "grid-server",
23-
version: "1.0.0",
24-
},
25-
{
26-
capabilities: {
27-
tools: {},
28-
resources: {},
29-
},
30-
},
31-
);
32-
33-
this.setupHandlers();
34-
}
35-
36-
private setupHandlers() {
37-
// List available tools.
38-
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
39-
return {
40-
tools: [queryWorkbook, getWorkbookChart, getWorkbookChartURL],
41-
};
42-
});
43-
44-
// Handle tool calls
45-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
46-
const { name, arguments: args } = request.params;
47-
48-
switch (name) {
49-
case "query_workbook":
50-
return await handleQueryWorkbook(args);
51-
case "get_workbook_chart":
52-
return await handleGetWorkbookChart(args);
53-
case "get_workbook_chart_url":
54-
return await handleGetWorkbookChartURL(args);
55-
}
17+
export const gridServer = new McpServer({
18+
name: "grid-server",
19+
version: "1.0.0",
20+
});
21+
22+
// Add tool to query data within an Excel workbook.
23+
gridServer.tool(
24+
"query_workbook",
25+
"Interact with a spreadsheet. Optionally, apply (update) values to cells. Read values from cells and formulas.",
26+
queryWorkbookInputSchema,
27+
handleQueryWorkbook,
28+
);
5629

57-
throw new Error(`Unknown tool: ${name}`);
58-
});
59-
}
30+
// Add tool to produce a chart from Excel workbook data.
31+
gridServer.tool(
32+
"get_workbook_chart",
33+
"Render a chart using workbook data",
34+
workbookChartInputSchema,
35+
handleGetWorkbookChart,
36+
);
6037

61-
async start() {
62-
const transport = new StdioServerTransport();
63-
await this.server.connect(transport);
64-
console.error("GRID MCP Server running on stdio");
65-
}
66-
}
38+
// Add tool to produce a URL to a chart containing Excel workbook data.
39+
gridServer.tool(
40+
"get_workbook_chart_url",
41+
"Get a URL that returns a chart image of workbook data",
42+
workbookChartURLInputSchema,
43+
handleGetWorkbookChartURL,
44+
);

src/server/handlers/handleGetWorkbookChart.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
import { workbookChartInputSchema } from "../../schemas/workbookChartInputSchema.js";
2-
import { fetchPNG } from "../../utils/fetch/fetchPNG.js";
1+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
32

4-
export async function handleGetWorkbookChart(args: Record<string, unknown> | undefined) {
5-
const validated = workbookChartInputSchema.parse(args);
3+
import type { WorkbookChartInputSchema } from "../../schemas/workbookChartInputSchema.js";
4+
import { fetchPNG } from "../../utils/fetch/fetchPNG.js";
65

6+
export async function handleGetWorkbookChart(
7+
args: WorkbookChartInputSchema,
8+
): Promise<CallToolResult> {
79
try {
810
const params = new URLSearchParams({
9-
data: validated.data,
10-
type: validated.type,
11+
data: args.data,
12+
type: args.type,
1113
});
12-
if (validated.title) {
13-
params.append("title", validated.title);
14+
if (args.title) {
15+
params.append("title", args.title);
1416
}
15-
if (validated.labels) {
16-
params.append("labels", validated.labels);
17+
if (args.labels) {
18+
params.append("labels", args.labels);
1719
}
18-
const url = `/v1/workbooks/${validated.id}/chart.png?${params.toString()}`;
20+
const url = `/v1/workbooks/${args.id}/chart.png?${params.toString()}`;
1921
const base64Image = await fetchPNG(url);
2022

2123
return {

src/server/handlers/handleGetWorkbookChartURL.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
1-
import { workbookChartURLInputSchema } from "../../schemas/workbookChartURLInputSchema.js";
1+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
3+
import type { WorkbookChartURLInputSchema } from "../../schemas/workbookChartURLInputSchema.js";
24
import { fetchJSON } from "../../utils/fetch/fetchJSON.js";
35

46
interface chartURLResponse {
57
url: string;
68
}
79

8-
export async function handleGetWorkbookChartURL(args: Record<string, unknown> | undefined) {
9-
const validated = workbookChartURLInputSchema.parse(args);
10-
10+
export async function handleGetWorkbookChartURL(
11+
args: WorkbookChartURLInputSchema,
12+
): Promise<CallToolResult> {
1113
try {
1214
const params = new URLSearchParams({
13-
data: validated.data,
14-
type: validated.type,
15+
data: args.data,
16+
type: args.type,
17+
imageType: args.imageType,
1518
});
16-
if (validated.title) {
17-
params.append("title", validated.title);
19+
if (args.title) {
20+
params.append("title", args.title);
1821
}
19-
if (validated.labels) {
20-
params.append("labels", validated.labels);
22+
if (args.labels) {
23+
params.append("labels", args.labels);
2124
}
22-
const url = `/v1/workbooks/${validated.id}/chart?${params.toString()}`;
25+
const url = `/v1/workbooks/${args.id}/chart?${params.toString()}`;
2326
const json = await fetchJSON<chartURLResponse>(url);
2427

2528
return {

src/server/handlers/handleQueryWorkbook.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import { QueryWorkbookInputSchema } from "../../schemas/queryWorkbookInputSchema.js";
2-
import { fetchJSON } from "../../utils/fetch/fetchJSON.js";
1+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
32

4-
export async function handleQueryWorkbook(args: Record<string, unknown> | undefined) {
5-
const validated = QueryWorkbookInputSchema.parse(args);
3+
import { type QueryWorkbookInputSchema } from "../../schemas/queryWorkbookInputSchema.js";
4+
import { fetchJSON } from "../../utils/fetch/fetchJSON.js";
65

6+
export async function handleQueryWorkbook({
7+
workbookId,
8+
read,
9+
apply,
10+
}: QueryWorkbookInputSchema): Promise<CallToolResult> {
711
try {
8-
const response = await fetchJSON(`/v1/workbooks/${validated.workbookId}/query`, {
12+
const response = await fetchJSON(`/v1/workbooks/${workbookId}/query`, {
913
method: "POST",
1014
body: JSON.stringify({
11-
read: validated.read,
12-
apply: validated.apply,
15+
read,
16+
apply,
1317
options: {
1418
structure: "table",
1519
values: "formatted",

src/server/tools/getWorkbookChart.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/server/tools/getWorkbookChartURL.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/server/tools/index.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/server/tools/queryWorkbook.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)