Skip to content

Commit 284cc0e

Browse files
authored
feat: MCP resources support (#47)
Signed-off-by: John McBride <[email protected]>
1 parent bf526de commit 284cc0e

File tree

12 files changed

+948
-4
lines changed

12 files changed

+948
-4
lines changed

examples/servers/prompts/package-lock.json

Lines changed: 21 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Resources Example Server
2+
3+
This example demonstrates how to use the Zuplo MCP SDK
4+
to create a server that exposes resources.
5+
6+
## Running the Example
7+
8+
```bash
9+
npm install
10+
npm run build
11+
npm start
12+
```
13+
14+
## Testing
15+
16+
### List available resources
17+
18+
```bash
19+
curl -X POST http://localhost:3000/mcp \
20+
-H "Content-Type: application/json" \
21+
-d '{
22+
"jsonrpc": "2.0",
23+
"id": 1,
24+
"method": "resources/list",
25+
"params": {}
26+
}'
27+
```
28+
29+
### Read a specific resource
30+
31+
```bash
32+
curl -X POST http://localhost:3000/mcp \
33+
-H "Content-Type: application/json" \
34+
-d '{
35+
"jsonrpc": "2.0",
36+
"id": 2,
37+
"method": "resources/read",
38+
"params": {
39+
"uri": "file:///example/document.txt"
40+
}
41+
}'
42+
```
43+
44+
### List resource templates
45+
46+
```bash
47+
curl -X POST http://localhost:3000/mcp \
48+
-H "Content-Type: application/json" \
49+
-d '{
50+
"jsonrpc": "2.0",
51+
"id": 3,
52+
"method": "resources/templates/list",
53+
"params": {}
54+
}'
55+
```
56+
57+
### Read a resource using a template
58+
59+
```bash
60+
curl -X POST http://localhost:3000/mcp \
61+
-H "Content-Type: application/json" \
62+
-d '{
63+
"jsonrpc": "2.0",
64+
"id": 4,
65+
"method": "resources/read",
66+
"params": {
67+
"uri": "file:///example/test.txt"
68+
}
69+
}'
70+
```

examples/servers/resources/package-lock.json

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@zuplo/mcp example resources server",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"build": "tsc",
7+
"start": "node dist/index.js",
8+
"clean": "rm -rf ./dist/ && rm -rf ./node_modules/"
9+
},
10+
"dependencies": {
11+
"@hono/node-server": "^1.14.1",
12+
"@zuplo/mcp": "file:../../../",
13+
"hono": "^4.7.9"
14+
}
15+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { serve } from "@hono/node-server";
2+
import { Hono } from "hono";
3+
import { MCPServer } from "@zuplo/mcp/server";
4+
import { HTTPStreamableTransport } from "@zuplo/mcp/transport/httpstreamable";
5+
import { ConsoleLogger } from "@zuplo/mcp/logger";
6+
import { ResourceMetadata, ResourceReader, URITemplate } from "@zuplo/mcp/resources";
7+
8+
const app = new Hono();
9+
10+
const logger = new ConsoleLogger();
11+
12+
const server = new MCPServer({
13+
name: "Resources Example Server",
14+
version: "1.0.0",
15+
logger,
16+
capabilities: {
17+
resources: {},
18+
},
19+
});
20+
21+
server.addResource(
22+
"document",
23+
"file:///example/document.txt",
24+
{
25+
title: "Example Document",
26+
description: "A sample text document",
27+
mimeType: "text/plain",
28+
annotations: {
29+
audience: ["user" as const, "assistant" as const],
30+
priority: 0.8,
31+
},
32+
},
33+
async (uri) => ({
34+
contents: [
35+
{
36+
uri,
37+
mimeType: "text/plain",
38+
text: "This is a sample document.\n\nIt contains multiple lines of text that demonstrate how resources work in the MCP protocol.",
39+
},
40+
],
41+
})
42+
);
43+
44+
server.addResource(
45+
"data",
46+
"file:///example/data.json",
47+
{
48+
title: "Sample Data",
49+
description: "JSON data file with sample information",
50+
mimeType: "application/json",
51+
annotations: {
52+
audience: ["assistant" as const],
53+
priority: 0.5,
54+
},
55+
},
56+
async (uri) => ({
57+
contents: [
58+
{
59+
uri,
60+
mimeType: "application/json",
61+
text: JSON.stringify(
62+
{
63+
name: "Example Data",
64+
items: [
65+
{ id: 1, value: "First item" },
66+
{ id: 2, value: "Second item" },
67+
{ id: 3, value: "Third item" },
68+
],
69+
metadata: {
70+
created: "2025-01-01",
71+
version: "1.0",
72+
},
73+
},
74+
null,
75+
2
76+
),
77+
},
78+
],
79+
})
80+
);
81+
82+
server.addResource(
83+
"image",
84+
"file:///example/image.png",
85+
{
86+
title: "Example Image",
87+
description: "A sample PNG image",
88+
mimeType: "image/png",
89+
},
90+
async (uri) => ({
91+
contents: [
92+
{
93+
uri,
94+
mimeType: "image/png",
95+
blob: "aW1hZ2Vwbmc=",
96+
},
97+
],
98+
})
99+
);
100+
101+
const uriTemp: URITemplate = { template: "file:///example/{filename}" };
102+
const resMetadata: ResourceMetadata = {
103+
title: "📁 Example Files",
104+
description: "Access any file in the example directory by name",
105+
annotations: {
106+
audience: ["user" as const],
107+
priority: 0.6,
108+
}
109+
};
110+
111+
const resHandler: ResourceReader = async (uri) => {
112+
const filename = uri.split("/").pop();
113+
return {
114+
contents: [
115+
{
116+
uri,
117+
mimeType: "text/plain",
118+
text: `This is a dynamically generated file: ${filename}`,
119+
},
120+
],
121+
};
122+
};
123+
124+
server.addResource(
125+
"files",
126+
uriTemp,
127+
resMetadata,
128+
resHandler
129+
);
130+
131+
const transport = new HTTPStreamableTransport({ logger });
132+
await transport.connect();
133+
server.withTransport(transport);
134+
135+
app.post("/mcp", async (c) => {
136+
try {
137+
const request = c.req.raw;
138+
return transport.handleRequest(request);
139+
} catch (error) {
140+
logger.error("Error handling MCP request:", error);
141+
return new Response(JSON.stringify({ error: "Internal server error" }), {
142+
status: 500,
143+
headers: { "Content-Type": "application/json" },
144+
});
145+
}
146+
});
147+
148+
logger.info("Resources MCP server starting on port 3000");
149+
serve({
150+
fetch: app.fetch,
151+
port: 3000
152+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "./dist",
5+
"rootDir": "./src"
6+
},
7+
"include": [
8+
"./src/**/*"
9+
],
10+
"references": [
11+
{ "path": "../../../" }
12+
]
13+
}

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" npx jest",
99
"lint": "npx @biomejs/[email protected] check src/",
1010
"lint:fix": "npx @biomejs/[email protected] check src/ --fix && prettier format . --write",
11-
"lint:fix-unsafe": "npx @biomejs/[email protected] check src/ --fix --unsafe",
12-
"format": "npx @biomejs/[email protected] format src/ --write"
11+
"lint:fix-unsafe": "npx @biomejs/[email protected] check src/ --fix --unsafe && prettier format . --write",
12+
"format": "npx @biomejs/[email protected] format src/ --write && prettier format . --write"
1313
},
1414
"files": [
1515
"dist/**/**",
@@ -74,6 +74,10 @@
7474
"types": "./dist/prompts/index.d.ts",
7575
"import": "./dist/prompts/index.js"
7676
},
77+
"./resources": {
78+
"types": "./dist/resources/index.d.ts",
79+
"import": "./dist/resources/index.js"
80+
},
7781
"./openapi/types": {
7882
"types": "./dist/openapi/types.d.ts",
7983
"import": "./dist/openapi/types.js"

src/jsonrpc2/consts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export enum ErrorCode {
2121
// SDK error codes
2222
ConnectionClosed = -32000,
2323
RequestTimeout = -32001,
24+
ResourceNotFound = -32002,
2425

2526
// Standard JSON-RPC error codes
2627
ParseError = -32700,

src/resources/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type {
2+
ResourceReader,
3+
ResourceMetadata,
4+
URITemplate,
5+
} from "./types.js";

0 commit comments

Comments
 (0)