Skip to content

Commit 3fc0efc

Browse files
authored
feat: add resources and meta to mcp manifests (#674)
Add resources to functions manifest for any resources that are defined on the mcp sdk. Additionally, add handleResources export to the mcp server template, so calls will be proxied for functions defined in the SDK
1 parent 5b8a324 commit 3fc0efc

File tree

5 files changed

+111
-11
lines changed

5 files changed

+111
-11
lines changed

.changeset/wet-banks-join.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@gram-ai/functions": minor
3+
---
4+
5+
allow for defining resources in mcp builds of gram functions

.mise-tasks/start/server.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
#MISE dir="{{ config_root }}/server"
44
#MISE description="Start up the API server"
5-
#MISE sources=["server/**/*.go"]
65

76
GIT_SHA=$(git rev-parse HEAD)
87

@@ -11,4 +10,4 @@ if [ -f "../config.local.toml" ]; then
1110
CONFIG_ARGS=(--config-file ../config.local.toml)
1211
fi
1312

14-
go run -ldflags="-X github.com/speakeasy-api/gram/server/cmd/gram.GitSHA=${GIT_SHA} -X goa.design/clue/health.Version=${GIT_SHA}" main.go start "${CONFIG_ARGS[@]}" "$@"
13+
go run -ldflags="-X github.com/speakeasy-api/gram/server/cmd/gram.GitSHA=${GIT_SHA} -X goa.design/clue/health.Version=${GIT_SHA}" main.go start "${CONFIG_ARGS[@]}" "$@"

ts-framework/create-function/gram-template-mcp/src/functions.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,20 @@ export async function handleToolCall(call: {
2626
headers: { "Content-Type": "application/json; mcp=tools_call" },
2727
});
2828
}
29+
30+
export async function handleResources(call: {
31+
uri: string;
32+
input: string;
33+
_meta?: Record<string, unknown>;
34+
}): Promise<Response> {
35+
const response = await client.readResource({
36+
uri: call.uri,
37+
_meta: call._meta,
38+
});
39+
40+
const body = JSON.stringify(response);
41+
return new Response(body, {
42+
status: 200,
43+
headers: { "Content-Type": "application/json" },
44+
});
45+
}

ts-framework/create-function/gram-template-mcp/src/mcp.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,24 @@ server.registerTool(
2020
};
2121
},
2222
);
23+
24+
server.registerResource(
25+
"a-cool-photo",
26+
"resources://a-cool-photo",
27+
{
28+
mimeType: "image/jpg",
29+
description: "This photo is really something",
30+
title: "A Cool Photo",
31+
},
32+
async (uri) => {
33+
let res = await fetch("https://picsum.photos/200/300.jpg");
34+
return {
35+
contents: [
36+
{
37+
uri: uri.href,
38+
blob: Buffer.from(await res.arrayBuffer()).toString("base64"),
39+
},
40+
],
41+
};
42+
},
43+
);

ts-framework/functions/src/build/mcp.ts

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { writeFile, open, stat, mkdir } from "node:fs/promises";
22
import { join, resolve } from "node:path";
33
import esbuild from "esbuild";
44
import archiver from "archiver";
5+
import {
6+
McpError,
7+
ErrorCode as McpErrorCode,
8+
} from "@modelcontextprotocol/sdk/types.js";
9+
import type { Client } from "@modelcontextprotocol/sdk/client";
510

611
export type BuildMCPServerResult = {
712
files: Array<{ path: string; size: number }>;
@@ -60,6 +65,14 @@ async function buildFunctionsManifest(options: {
6065
name: string;
6166
description?: string | undefined;
6267
inputSchema: unknown;
68+
_meta?: unknown;
69+
}>;
70+
resources?: Array<{
71+
name: string;
72+
description?: string | undefined;
73+
uri: string;
74+
mimeType?: string | undefined;
75+
_meta?: unknown;
6376
}>;
6477
}> {
6578
const cwd = options.cwd ?? process.cwd();
@@ -92,16 +105,61 @@ async function buildFunctionsManifest(options: {
92105
await server.connect(serverTransport);
93106
await mcpClient.connect(clientTransport);
94107

95-
const res = await mcpClient.listTools();
96-
const tools = res.tools.map((tool) => {
97-
return {
98-
name: tool.name,
99-
description: tool.description,
100-
inputSchema: tool.inputSchema,
101-
};
102-
});
108+
let tools = await collectTools(mcpClient);
109+
let resources = await collectResources(mcpClient);
110+
111+
return { version: "0.0.0", tools, resources };
112+
}
113+
114+
async function collectTools(client: Client) {
115+
try {
116+
const res = await client.listTools();
117+
return res.tools.map((tool) => {
118+
return {
119+
name: tool.name,
120+
description: tool.description,
121+
inputSchema: tool.inputSchema,
122+
meta: {
123+
"gram.ai/kind": "mcp-passthrough",
124+
...tool._meta,
125+
},
126+
};
127+
});
128+
} catch (err) {
129+
if (err instanceof McpError && err.code === McpErrorCode.MethodNotFound) {
130+
console.warn("No tools registered");
131+
} else {
132+
throw err;
133+
}
134+
return [];
135+
}
136+
}
137+
138+
async function collectResources(client: Client) {
139+
try {
140+
const resourcesResponse = await client.listResources();
141+
return resourcesResponse.resources.map((resource) => {
142+
return {
143+
name: resource.name,
144+
description: resource.description,
145+
uri: resource.uri,
146+
mimeType: resource.mimeType,
147+
title: resource.title,
148+
meta: {
149+
"gram.ai/kind": "mcp-passthrough",
150+
...resource._meta,
151+
},
152+
};
153+
});
154+
} catch (err) {
155+
if (err instanceof McpError && err.code === McpErrorCode.MethodNotFound) {
156+
console.warn("No tools registered");
157+
} else {
158+
throw err;
159+
}
160+
}
103161

104-
return { version: "0.0.0", tools };
162+
return [];
105163
}
106164

107165
async function bundleFunction(options: {

0 commit comments

Comments
 (0)