funplay-godot-mcp 0.7.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/README.md +40 -0
- package/bin/funplay-godot-mcp.js +209 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Funplay Godot MCP stdio wrapper
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.FunplayAI/funplay-godot-mcp -->
|
|
4
|
+
|
|
5
|
+
This package bridges MCP stdio clients to the local HTTP server started by the Funplay MCP for Godot editor addon.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Start Godot, enable the addon, and make sure the Funplay MCP server is running. For local development, link the wrapper first:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm link
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
funplay-godot-mcp --url http://127.0.0.1:8765/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
You can also configure the endpoint through an environment variable:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
FUNPLAY_GODOT_MCP_URL=http://127.0.0.1:8765/ funplay-godot-mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## MCP client config
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"funplay": {
|
|
33
|
+
"command": "funplay-godot-mcp",
|
|
34
|
+
"env": {
|
|
35
|
+
"FUNPLAY_GODOT_MCP_URL": "http://127.0.0.1:8765/"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { argv, env, exit, stderr, stdin, stdout } from "node:process";
|
|
4
|
+
|
|
5
|
+
const VERSION = "0.7.0";
|
|
6
|
+
const DEFAULT_URL = "http://127.0.0.1:8765/";
|
|
7
|
+
|
|
8
|
+
const options = parseArgs(argv.slice(2));
|
|
9
|
+
if (options.help) {
|
|
10
|
+
stdout.write(buildHelp());
|
|
11
|
+
exit(0);
|
|
12
|
+
}
|
|
13
|
+
if (options.version) {
|
|
14
|
+
stdout.write(`${VERSION}\n`);
|
|
15
|
+
exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const endpoint = normalizeEndpoint(
|
|
19
|
+
options.url || env.FUNPLAY_GODOT_MCP_URL || env.GODOT_MCP_URL || DEFAULT_URL,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
let buffer = "";
|
|
23
|
+
let queue = Promise.resolve();
|
|
24
|
+
|
|
25
|
+
stdin.setEncoding("utf8");
|
|
26
|
+
stdin.on("data", (chunk) => {
|
|
27
|
+
buffer += chunk;
|
|
28
|
+
drainBuffer(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
stdin.on("end", () => {
|
|
32
|
+
drainBuffer(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
stdin.on("error", (error) => {
|
|
36
|
+
stderr.write(`[funplay-godot-mcp] stdin error: ${error.message}\n`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function drainBuffer(flush) {
|
|
40
|
+
while (true) {
|
|
41
|
+
const index = buffer.indexOf("\n");
|
|
42
|
+
if (index < 0) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
const line = buffer.slice(0, index);
|
|
46
|
+
buffer = buffer.slice(index + 1);
|
|
47
|
+
enqueueLine(line);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (flush && buffer.trim() !== "") {
|
|
51
|
+
enqueueLine(buffer);
|
|
52
|
+
buffer = "";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function enqueueLine(line) {
|
|
57
|
+
queue = queue
|
|
58
|
+
.then(() => handleLine(line))
|
|
59
|
+
.catch((error) => {
|
|
60
|
+
stderr.write(`[funplay-godot-mcp] bridge error: ${error.stack || error.message}\n`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function handleLine(line) {
|
|
65
|
+
const trimmed = line.trim();
|
|
66
|
+
if (trimmed === "") {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let message;
|
|
71
|
+
try {
|
|
72
|
+
message = JSON.parse(trimmed);
|
|
73
|
+
} catch {
|
|
74
|
+
writeMessage({
|
|
75
|
+
jsonrpc: "2.0",
|
|
76
|
+
id: null,
|
|
77
|
+
error: {
|
|
78
|
+
code: -32700,
|
|
79
|
+
message: "Parse error",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (Array.isArray(message)) {
|
|
86
|
+
const responses = [];
|
|
87
|
+
for (const item of message) {
|
|
88
|
+
const response = await forwardMessage(item);
|
|
89
|
+
if (response !== null) {
|
|
90
|
+
responses.push(response);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (responses.length > 0) {
|
|
94
|
+
writeMessage(responses);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const response = await forwardMessage(message);
|
|
100
|
+
if (response !== null) {
|
|
101
|
+
writeMessage(response);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function forwardMessage(message) {
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch(endpoint, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"accept": "application/json",
|
|
111
|
+
"content-type": "application/json",
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(message),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (response.status === 204) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const text = await response.text();
|
|
121
|
+
if (text.trim() === "") {
|
|
122
|
+
return buildErrorFor(message, -32603, `Godot MCP returned HTTP ${response.status} with an empty body`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(text);
|
|
127
|
+
} catch {
|
|
128
|
+
return buildErrorFor(message, -32603, `Godot MCP returned invalid JSON over HTTP ${response.status}`);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return buildErrorFor(
|
|
132
|
+
message,
|
|
133
|
+
-32000,
|
|
134
|
+
`Failed to reach Godot MCP at ${endpoint}: ${error.message}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildErrorFor(message, code, messageText) {
|
|
140
|
+
if (!isJsonRpcRequest(message)) {
|
|
141
|
+
stderr.write(`[funplay-godot-mcp] ${messageText}\n`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
jsonrpc: "2.0",
|
|
147
|
+
id: message.id,
|
|
148
|
+
error: {
|
|
149
|
+
code,
|
|
150
|
+
message: messageText,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isJsonRpcRequest(value) {
|
|
156
|
+
return value !== null
|
|
157
|
+
&& typeof value === "object"
|
|
158
|
+
&& !Array.isArray(value)
|
|
159
|
+
&& Object.prototype.hasOwnProperty.call(value, "id");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function writeMessage(message) {
|
|
163
|
+
stdout.write(`${JSON.stringify(message)}\n`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeEndpoint(value) {
|
|
167
|
+
const trimmed = String(value || "").trim();
|
|
168
|
+
return trimmed === "" ? DEFAULT_URL : trimmed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseArgs(args) {
|
|
172
|
+
const parsed = {
|
|
173
|
+
help: false,
|
|
174
|
+
version: false,
|
|
175
|
+
url: "",
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
179
|
+
const arg = args[i];
|
|
180
|
+
if (arg === "--help" || arg === "-h") {
|
|
181
|
+
parsed.help = true;
|
|
182
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
183
|
+
parsed.version = true;
|
|
184
|
+
} else if (arg === "--url") {
|
|
185
|
+
parsed.url = args[i + 1] || "";
|
|
186
|
+
i += 1;
|
|
187
|
+
} else if (arg.startsWith("--url=")) {
|
|
188
|
+
parsed.url = arg.slice("--url=".length);
|
|
189
|
+
} else {
|
|
190
|
+
stderr.write(`[funplay-godot-mcp] Unknown argument ignored: ${arg}\n`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildHelp() {
|
|
198
|
+
return `Funplay MCP for Godot stdio bridge ${VERSION}
|
|
199
|
+
|
|
200
|
+
Usage:
|
|
201
|
+
funplay-godot-mcp [--url http://127.0.0.1:8765/]
|
|
202
|
+
|
|
203
|
+
Environment:
|
|
204
|
+
FUNPLAY_GODOT_MCP_URL Godot MCP HTTP endpoint
|
|
205
|
+
GODOT_MCP_URL Compatibility endpoint fallback
|
|
206
|
+
|
|
207
|
+
The Godot editor addon must be enabled and its MCP server must be running.
|
|
208
|
+
`;
|
|
209
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "funplay-godot-mcp",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "stdio bridge for the local Funplay MCP for Godot editor addon.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"mcpName": "io.github.FunplayAI/funplay-godot-mcp",
|
|
8
|
+
"bin": {
|
|
9
|
+
"funplay-godot-mcp": "bin/funplay-godot-mcp.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"README.md",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"godot",
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"funplay"
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/FunplayAI/funplay-godot-mcp.git",
|
|
28
|
+
"directory": "stdio-wrapper"
|
|
29
|
+
}
|
|
30
|
+
}
|