gopeak 2.0.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/LICENSE +21 -0
- package/README.md +909 -0
- package/build/addon/auto_reload/auto_reload.gd +126 -0
- package/build/addon/auto_reload/auto_reload.gd.uid +1 -0
- package/build/addon/auto_reload/plugin.cfg +7 -0
- package/build/addon/godot_mcp_runtime/godot_mcp_runtime.gd +33 -0
- package/build/addon/godot_mcp_runtime/godot_mcp_runtime.gd.uid +1 -0
- package/build/addon/godot_mcp_runtime/mcp_runtime_autoload.gd +616 -0
- package/build/addon/godot_mcp_runtime/mcp_runtime_autoload.gd.uid +1 -0
- package/build/addon/godot_mcp_runtime/plugin.cfg +7 -0
- package/build/dap_client.js +506 -0
- package/build/gdscript_utils.js +121 -0
- package/build/index.js +6275 -0
- package/build/lsp_client.js +521 -0
- package/build/project_utils.js +82 -0
- package/build/providers/ambientcg.js +174 -0
- package/build/providers/index.js +5 -0
- package/build/providers/kenney.js +159 -0
- package/build/providers/manager.js +122 -0
- package/build/providers/polyhaven.js +191 -0
- package/build/providers/types.js +19 -0
- package/build/resources.js +189 -0
- package/build/scripts/godot_operations.gd +6811 -0
- package/build/scripts/godot_operations.gd.uid +1 -0
- package/build/tools.js +89 -0
- package/package.json +82 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { extname, isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
const STATIC_RESOURCES = [
|
|
5
|
+
{
|
|
6
|
+
uri: 'godot://project/info',
|
|
7
|
+
name: 'Project Info',
|
|
8
|
+
description: 'Parsed Godot project.godot metadata as JSON.',
|
|
9
|
+
mimeType: 'application/json',
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
const RESOURCE_TEMPLATES = [
|
|
13
|
+
{
|
|
14
|
+
uriTemplate: 'godot://scene/{path}',
|
|
15
|
+
name: 'Scene File',
|
|
16
|
+
description: 'Read a Godot scene (.tscn) file from the current project.',
|
|
17
|
+
mimeType: 'text/plain',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
uriTemplate: 'godot://script/{path}',
|
|
21
|
+
name: 'GDScript File',
|
|
22
|
+
description: 'Read a GDScript (.gd) file from the current project.',
|
|
23
|
+
mimeType: 'text/x-gdscript',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
uriTemplate: 'godot://resource/{path}',
|
|
27
|
+
name: 'Resource File',
|
|
28
|
+
description: 'Read a project resource file (.tres, .tscn, .gd).',
|
|
29
|
+
mimeType: 'text/plain',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
function ensureProjectPath(getProjectPath) {
|
|
33
|
+
const projectPath = getProjectPath();
|
|
34
|
+
if (!projectPath) {
|
|
35
|
+
throw new Error('Project path is not set. Set a Godot project path first.');
|
|
36
|
+
}
|
|
37
|
+
return resolve(projectPath);
|
|
38
|
+
}
|
|
39
|
+
function validateRelativePath(inputPath) {
|
|
40
|
+
const normalized = inputPath.replace(/\\/g, '/').trim().replace(/^\/+/, '');
|
|
41
|
+
if (!normalized) {
|
|
42
|
+
throw new Error('Resource path is empty.');
|
|
43
|
+
}
|
|
44
|
+
if (normalized.includes('..')) {
|
|
45
|
+
throw new Error('Invalid resource path: directory traversal is not allowed.');
|
|
46
|
+
}
|
|
47
|
+
const segments = normalized.split('/');
|
|
48
|
+
if (segments.some((segment) => segment === '' || segment === '.' || segment === '..')) {
|
|
49
|
+
throw new Error('Invalid resource path.');
|
|
50
|
+
}
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
function resolveProjectFile(projectPath, resourcePath) {
|
|
54
|
+
const fullPath = resolve(projectPath, resourcePath);
|
|
55
|
+
const relativePath = relative(projectPath, fullPath);
|
|
56
|
+
if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
|
|
57
|
+
throw new Error('Resolved file path escapes project directory.');
|
|
58
|
+
}
|
|
59
|
+
return fullPath;
|
|
60
|
+
}
|
|
61
|
+
function parseGodotUri(uri) {
|
|
62
|
+
if (uri === 'godot://project/info') {
|
|
63
|
+
return { kind: 'project-info' };
|
|
64
|
+
}
|
|
65
|
+
let parsed;
|
|
66
|
+
try {
|
|
67
|
+
parsed = new URL(uri);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new Error(`Invalid URI: ${uri}`);
|
|
71
|
+
}
|
|
72
|
+
if (parsed.protocol !== 'godot:') {
|
|
73
|
+
throw new Error(`Unsupported URI scheme: ${parsed.protocol}`);
|
|
74
|
+
}
|
|
75
|
+
const host = parsed.hostname;
|
|
76
|
+
if (host !== 'scene' && host !== 'script' && host !== 'resource') {
|
|
77
|
+
throw new Error(`Unsupported Godot resource type: ${host}`);
|
|
78
|
+
}
|
|
79
|
+
const resourcePath = validateRelativePath(decodeURIComponent(parsed.pathname));
|
|
80
|
+
return { kind: host, resourcePath };
|
|
81
|
+
}
|
|
82
|
+
function ensureAllowedExtension(kind, filePath) {
|
|
83
|
+
const extension = extname(filePath).toLowerCase();
|
|
84
|
+
if (kind === 'scene' && extension !== '.tscn') {
|
|
85
|
+
throw new Error('Scene resources must use .tscn extension.');
|
|
86
|
+
}
|
|
87
|
+
if (kind === 'script' && extension !== '.gd') {
|
|
88
|
+
throw new Error('Script resources must use .gd extension.');
|
|
89
|
+
}
|
|
90
|
+
if (kind === 'resource' && !['.tres', '.tscn', '.gd'].includes(extension)) {
|
|
91
|
+
throw new Error('Resource URIs support only .tres, .tscn, and .gd files.');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function parseProjectGodot(content) {
|
|
95
|
+
const result = {};
|
|
96
|
+
let currentSection = 'root';
|
|
97
|
+
result[currentSection] = {};
|
|
98
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
99
|
+
const line = rawLine.trim();
|
|
100
|
+
if (!line || line.startsWith(';') || line.startsWith('#')) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (line.startsWith('[') && line.endsWith(']')) {
|
|
104
|
+
currentSection = line.slice(1, -1).trim();
|
|
105
|
+
if (!result[currentSection]) {
|
|
106
|
+
result[currentSection] = {};
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const eqIndex = line.indexOf('=');
|
|
111
|
+
if (eqIndex === -1) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const key = line.slice(0, eqIndex).trim();
|
|
115
|
+
const rawValue = line.slice(eqIndex + 1).trim();
|
|
116
|
+
result[currentSection][key] = parseIniLikeValue(rawValue);
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
function parseIniLikeValue(value) {
|
|
121
|
+
if (value === 'null') {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (value === 'true') {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (value === 'false') {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (/^-?\d+$/.test(value)) {
|
|
131
|
+
return Number.parseInt(value, 10);
|
|
132
|
+
}
|
|
133
|
+
if (/^-?\d*\.\d+$/.test(value)) {
|
|
134
|
+
return Number.parseFloat(value);
|
|
135
|
+
}
|
|
136
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
137
|
+
return value.slice(1, -1);
|
|
138
|
+
}
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
function readResourceText(uri, getProjectPath) {
|
|
142
|
+
const projectPath = ensureProjectPath(getProjectPath);
|
|
143
|
+
const parsedUri = parseGodotUri(uri);
|
|
144
|
+
if (parsedUri.kind === 'project-info') {
|
|
145
|
+
const projectFilePath = resolveProjectFile(projectPath, 'project.godot');
|
|
146
|
+
const rawProject = readFileSync(projectFilePath, 'utf-8');
|
|
147
|
+
const parsedProject = parseProjectGodot(rawProject);
|
|
148
|
+
return {
|
|
149
|
+
mimeType: 'application/json',
|
|
150
|
+
text: JSON.stringify(parsedProject, null, 2),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
154
|
+
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
155
|
+
const text = readFileSync(filePath, 'utf-8');
|
|
156
|
+
return {
|
|
157
|
+
mimeType: parsedUri.kind === 'script' ? 'text/x-gdscript' : 'text/plain',
|
|
158
|
+
text,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export function setupResourceHandlers(server, getProjectPath) {
|
|
162
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
163
|
+
resources: STATIC_RESOURCES,
|
|
164
|
+
}));
|
|
165
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
166
|
+
resourceTemplates: RESOURCE_TEMPLATES,
|
|
167
|
+
}));
|
|
168
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
169
|
+
const uri = request.params.uri;
|
|
170
|
+
try {
|
|
171
|
+
const { mimeType, text } = readResourceText(uri, getProjectPath);
|
|
172
|
+
return {
|
|
173
|
+
contents: [
|
|
174
|
+
{
|
|
175
|
+
uri,
|
|
176
|
+
mimeType,
|
|
177
|
+
text,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if (error instanceof Error) {
|
|
184
|
+
throw new Error(`Failed to read resource '${uri}': ${error.message}`);
|
|
185
|
+
}
|
|
186
|
+
throw new Error(`Failed to read resource '${uri}'.`);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|