@gram-ai/create-function 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gram-template-gram/NEXT_STEPS.txt +2 -0
- package/gram-template-gram/README.md +4 -8
- package/gram-template-gram/node_modules/.bin/mcp-inspector +21 -0
- package/gram-template-gram/package.json +3 -3
- package/gram-template-gram/src/server.ts +117 -36
- package/gram-template-mcp/src/functions.ts +17 -0
- package/gram-template-mcp/src/mcp.ts +21 -0
- package/package.json +2 -2
|
@@ -2,6 +2,8 @@ All done! Jump in with `cd $DIR`.
|
|
|
2
2
|
|
|
3
3
|
Some next steps:
|
|
4
4
|
|
|
5
|
+
- Start a local development MCP server with `$PACKAGE_MANAGER run dev`
|
|
6
|
+
|
|
5
7
|
- Ensure the `Gram CLI` is installed and authenticated (`gram auth`): https://docs.getgram.ai/command-line/installation
|
|
6
8
|
|
|
7
9
|
- Build your function with `$PACKAGE_MANAGER run build`
|
|
@@ -40,20 +40,16 @@ pnpm build
|
|
|
40
40
|
## Testing Locally
|
|
41
41
|
|
|
42
42
|
If you want to poke at the tools you've built during local development, you can
|
|
43
|
-
start a
|
|
43
|
+
start a local MCP server over stdio transport with:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
pnpm dev
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Specifically, this command will spin up [MCP inspector][mcp-inspector] to let
|
|
50
|
+
you interactively test your tools.
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
curl \
|
|
53
|
-
--data '{"name": "greet", "input": {"name": "Georges"}}' \
|
|
54
|
-
-H "Content-Type: application/json" \
|
|
55
|
-
http://localhost:3000/tool-call
|
|
56
|
-
```
|
|
52
|
+
[mcp-inspector]: https://github.com/modelcontextprotocol/inspector
|
|
57
53
|
|
|
58
54
|
## What next?
|
|
59
55
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/inspector/cli/build/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/inspector/cli/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/inspector/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/inspector/cli/build/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/inspector/cli/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/inspector/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules/@modelcontextprotocol/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/@modelcontextprotocol+inspector@0.17.1_@types+node@22.18.6_@types+react-dom@19.1.9_@typ_cc52197392af7d7a50d2088d86700a7d/node_modules:/home/runner/work/gram/gram/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../@modelcontextprotocol/inspector/cli/build/cli.js" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../@modelcontextprotocol/inspector/cli/build/cli.js" "$@"
|
|
21
|
+
fi
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=22.18.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"dev": "node ./src/server.ts",
|
|
22
|
+
"dev": "mcp-inspector node ./src/server.ts",
|
|
23
23
|
"lint": "tsc --noEmit",
|
|
24
24
|
"build": "node -e \"console.log('Stubbed out')\"",
|
|
25
25
|
"_:build": "gram-build",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@gram-ai/functions": "workspace:",
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
31
32
|
"zod": "^4"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
+
"@modelcontextprotocol/inspector": "^0.17.1",
|
|
35
36
|
"@types/node": "22.x",
|
|
36
|
-
"hono": "^4",
|
|
37
37
|
"typescript": "^5"
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -1,44 +1,125 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import {
|
|
3
|
+
CallToolRequestSchema,
|
|
4
|
+
ListToolsRequestSchema,
|
|
5
|
+
type CallToolResult,
|
|
6
|
+
type ListToolsResult,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
|
|
10
|
+
import pkg from "../package.json" with { type: "json" };
|
|
3
11
|
import { gram } from "./gram.ts";
|
|
4
12
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
const structuredLike = /\b(yaml|yml|json|toml|xml|xhtml)\b/i;
|
|
14
|
+
const textLike = /^text\//i;
|
|
15
|
+
const imageLike = /^image\//i;
|
|
16
|
+
const audioLike = /^audio\//i;
|
|
17
|
+
|
|
18
|
+
export const server = new Server(
|
|
19
|
+
{
|
|
20
|
+
name: pkg.name,
|
|
21
|
+
version: pkg.version,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
capabilities: {
|
|
25
|
+
tools: {},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
server.setRequestHandler(
|
|
31
|
+
ListToolsRequestSchema,
|
|
32
|
+
async (): Promise<ListToolsResult> => {
|
|
33
|
+
const tools = gram.manifest().tools.map((t) => {
|
|
34
|
+
return {
|
|
35
|
+
name: t.name,
|
|
36
|
+
description: t.description,
|
|
37
|
+
inputSchema: t.inputSchema,
|
|
38
|
+
};
|
|
39
|
+
}) as ListToolsResult["tools"];
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
tools,
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
server.setRequestHandler(
|
|
48
|
+
CallToolRequestSchema,
|
|
49
|
+
async (req, extra): Promise<CallToolResult> => {
|
|
50
|
+
const { name, arguments: args } = req.params;
|
|
51
|
+
|
|
52
|
+
const resp = await gram.handleToolCall({ name, input: args } as any, {
|
|
53
|
+
signal: extra.signal,
|
|
34
54
|
});
|
|
55
|
+
|
|
56
|
+
let ctype = resp.headers.get("Content-Type") || "";
|
|
57
|
+
ctype = ctype.split(";")[0]?.trim() || "";
|
|
58
|
+
|
|
59
|
+
switch (true) {
|
|
60
|
+
case textLike.test(ctype) || structuredLike.test(ctype): {
|
|
61
|
+
const text = await resp.text();
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text }],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
case imageLike.test(ctype): {
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "image",
|
|
71
|
+
mimeType: ctype,
|
|
72
|
+
data: await responseToBase64(resp),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
case audioLike.test(ctype): {
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: "audio",
|
|
82
|
+
mimeType: ctype,
|
|
83
|
+
data: await responseToBase64(resp),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
default: {
|
|
89
|
+
return {
|
|
90
|
+
isError: true,
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: `Unhandled content type: ${ctype}. Create a handler for this type in the MCP server.`,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
async function responseToBase64(resp: Response): Promise<string> {
|
|
104
|
+
const blob = await resp.arrayBuffer();
|
|
105
|
+
const buffer = Buffer.from(blob);
|
|
106
|
+
return buffer.toString("base64");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function run() {
|
|
110
|
+
console.error("Starting MCP server with stdio...");
|
|
111
|
+
const stdio = new StdioServerTransport();
|
|
112
|
+
await server.connect(stdio);
|
|
113
|
+
|
|
114
|
+
const quit = async () => {
|
|
115
|
+
console.error("\nShutting down MCP server...");
|
|
116
|
+
await server.close();
|
|
117
|
+
process.exit(0);
|
|
35
118
|
};
|
|
36
|
-
process.
|
|
37
|
-
process.
|
|
119
|
+
process.once("SIGINT", quit);
|
|
120
|
+
process.once("SIGTERM", quit);
|
|
38
121
|
}
|
|
39
122
|
|
|
40
123
|
if (import.meta.main) {
|
|
41
|
-
|
|
124
|
+
run();
|
|
42
125
|
}
|
|
43
|
-
|
|
44
|
-
export default app;
|
|
@@ -26,3 +26,20 @@ export async function handleToolCall(call: {
|
|
|
26
26
|
headers: { "Content-Type": "application/json; mcp=tools_call" },
|
|
27
27
|
});
|
|
28
28
|
}
|
|
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
|
+
}
|
|
@@ -20,3 +20,24 @@ server.registerTool(
|
|
|
20
20
|
};
|
|
21
21
|
},
|
|
22
22
|
);
|
|
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
|
+
);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gram-ai/create-function",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Build AI tools and deploy them to getgram.ai",
|
|
6
6
|
"keywords": [],
|
|
7
7
|
"homepage": "https://github.com/speakeasy-api/gram",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"prettier": "^3.6.2",
|
|
39
39
|
"typescript": "5.9.2",
|
|
40
40
|
"zod": "^3.25.76",
|
|
41
|
-
"@gram-ai/functions": "^0.
|
|
41
|
+
"@gram-ai/functions": "^0.5.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc --noEmit false"
|